aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--settings.gradle.kts2
-rw-r--r--subprojects/store-dse/build.gradle.kts16
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/ActionFactory.java15
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/ModificationAdapter.java24
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/ModificationBuilder.java11
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/ModificationStoreAdapter.java11
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/internal/ModificationAdapterImpl.java62
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/internal/ModificationBuilderImpl.java29
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/internal/ModificationStoreAdapterImpl.java29
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstExplorer.java164
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstStoreManager.java72
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstWorker.java113
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/SubmitResult.java14
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationAdapter.java31
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationBuilder.java59
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationStoreAdapter.java27
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/ObjectiveValue.java24
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/ObjectiveValues.java69
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/Transformation.java44
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/TransformationRule.java63
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/VersionWithObjectiveValue.java11
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationAdapterImpl.java90
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationBuilderImpl.java75
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationStoreAdapterImpl.java70
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Criterion.java15
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/CriterionCalculator.java10
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Objective.java15
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/ObjectiveCalculator.java10
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/QueryCriteria.java44
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/QueryObjective.java44
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/ActivationStore.java18
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/EquivalenceClassStore.java16
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/ObjectivePriorityQueue.java21
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/SolutionStore.java17
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/AbstractEquivalenceClassStore.java47
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreBitVectorEntry.java46
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreEntry.java32
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreImpl.java131
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreListEntry.java95
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreWorker.java56
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/CompleteEquivalenceClassStore.java104
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/FastEquivalenceClassStore.java32
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ObjectivePriorityQueueImpl.java74
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/SolutionStoreImpl.java53
-rw-r--r--subprojects/store-dse/src/test/java/tools/refinery/store/dse/CRAExamplesTest.java277
-rw-r--r--subprojects/store-dse/src/test/java/tools/refinery/store/dse/DebugTest.java116
-rw-r--r--subprojects/store-dse/src/test/java/tools/refinery/store/dse/DesignSpaceExplorationTest.java596
-rw-r--r--subprojects/store-dse/src/test/java/tools/refinery/store/dse/TransformationRuleTest.java414
-rw-r--r--subprojects/store-dse/src/test/java/tools/refinery/store/dse/tests/QueryAssertions.java57
-rw-r--r--subprojects/store-dse/src/test/java/tools/refinery/store/dse/transition/statespace/internal/ActivationUnitTest.java72
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java3
-rw-r--r--subprojects/store/src/jmh/java/tools/refinery/store/map/benchmarks/ImmutablePutExecutionPlan.java19
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/AnyVersionedMap.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/ContinuousHashProvider.java (renamed from subprojects/store/src/main/java/tools/refinery/store/map/ContinousHashProvider.java)14
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/IteratorAsCursor.java64
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/Version.java26
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/Versioned.java18
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/VersionedMap.java4
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java18
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactory.java24
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java30
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java132
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/HashClash.java23
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java229
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java90
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java150
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaBasedVersionedMapStoreFactory.java38
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaDiffCursor.java147
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapDelta.java20
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapTransaction.java41
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaArrayStore.java36
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaMapStore.java53
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaStore.java29
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java237
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapStoreDeltaImpl.java94
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/ImmutableNode.java (renamed from subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java)163
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/InOrderMapCursor.java146
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MapCursor.java (renamed from subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java)54
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MapDiffCursor.java264
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MutableNode.java (renamed from subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java)254
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/Node.java131
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/OldValueBox.java (renamed from subprojects/store/src/main/java/tools/refinery/store/map/internal/OldValueBox.java)6
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/StateBasedVersionedMapStoreFactory.java43
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStateImpl.java (renamed from subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java)62
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateConfiguration.java (renamed from subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreConfiguration.java)25
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateImpl.java119
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java3
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/Model.java10
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java4
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/ModelStore.java8
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java127
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProviderBitMagic.java34
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/IndexedVersionedInterpretation.java6
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelImpl.java66
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java18
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java37
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelVersion.java29
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/NullaryVersionedInterpretation.java6
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/UnaryVersionedInterpretation.java6
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java36
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/Morphism.java11
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/ObjectCode.java11
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculator.java10
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculatorFactory.java15
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderAdapter.java23
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderBuilder.java45
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderResult.java9
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderStoreAdapter.java17
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java21
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderAdapterImpl.java39
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderBuilderImpl.java71
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderStoreAdapterImpl.java74
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java96
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java193
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java115
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCodeImpl.java81
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairing.java89
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairingPermutations.java57
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/NodePairing.java33
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/PermutationMorphism.java64
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java166
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/TrivialNodePairing.java36
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/InOrderCursorTest.java56
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/MapUnitTests.java75
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java70
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java57
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java161
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java91
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java58
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java60
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java50
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java66
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java72
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SingleThreadFuzzTest.java66
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java53
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtils.java12
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtilsTest.java19
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java84
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/model/hashtests/HashEfficiencyTest.java13
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/model/tests/ModelTest.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/statecoding/EquivalenceTest.java217
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/statecoding/ExperimentalSetupTest.java199
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderBuildTest.java171
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderUnitTest.java195
-rw-r--r--subprojects/visualization/build.gradle.kts13
-rw-r--r--subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerAdapter.java32
-rw-r--r--subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerBuilder.java16
-rw-r--r--subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerStoreAdapter.java22
-rw-r--r--subprojects/visualization/src/main/java/tools/refinery/visualization/internal/FileFormat.java26
-rw-r--r--subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizeStoreAdapterImpl.java60
-rw-r--r--subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerAdapterImpl.java387
-rw-r--r--subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerBuilderImpl.java55
152 files changed, 9131 insertions, 1344 deletions
diff --git a/settings.gradle.kts b/settings.gradle.kts
index d0f82de8..075faa7c 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -14,6 +14,7 @@ include(
14 "language-semantics", 14 "language-semantics",
15 "language-web", 15 "language-web",
16 "store", 16 "store",
17 "store-dse",
17 "store-query", 18 "store-query",
18 "store-query-viatra", 19 "store-query-viatra",
19 "store-reasoning", 20 "store-reasoning",
@@ -22,6 +23,7 @@ include(
22 "viatra-runtime-localsearch", 23 "viatra-runtime-localsearch",
23 "viatra-runtime-rete", 24 "viatra-runtime-rete",
24 "viatra-runtime-rete-recipes", 25 "viatra-runtime-rete-recipes",
26 "visualization",
25) 27)
26 28
27for (project in rootProject.children) { 29for (project in rootProject.children) {
diff --git a/subprojects/store-dse/build.gradle.kts b/subprojects/store-dse/build.gradle.kts
new file mode 100644
index 00000000..5734fa09
--- /dev/null
+++ b/subprojects/store-dse/build.gradle.kts
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7plugins {
8 id("tools.refinery.gradle.java-library")
9}
10
11dependencies {
12 api(project(":refinery-store-query"))
13 implementation(libs.eclipseCollections.api)
14 runtimeOnly(libs.eclipseCollections)
15 testImplementation(project(":refinery-store-query-viatra"))
16}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/ActionFactory.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/ActionFactory.java
new file mode 100644
index 00000000..48a508b4
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/ActionFactory.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.tuple.Tuple;
10
11import java.util.function.Consumer;
12
13public interface ActionFactory {
14 Consumer<Tuple> prepare(Model model);
15}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/ModificationAdapter.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/ModificationAdapter.java
new file mode 100644
index 00000000..f15c16e0
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/ModificationAdapter.java
@@ -0,0 +1,24 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.modification;
7
8import tools.refinery.store.adapter.ModelAdapter;
9import tools.refinery.store.dse.modification.internal.ModificationBuilderImpl;
10import tools.refinery.store.tuple.Tuple;
11import tools.refinery.store.tuple.Tuple1;
12
13public interface ModificationAdapter extends ModelAdapter {
14
15 int getModelSize();
16
17 Tuple1 createObject();
18
19 Tuple deleteObject(Tuple tuple);
20
21 static ModificationBuilder builder() {
22 return new ModificationBuilderImpl();
23 }
24}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/ModificationBuilder.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/ModificationBuilder.java
new file mode 100644
index 00000000..48c22bdf
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/ModificationBuilder.java
@@ -0,0 +1,11 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.modification;
7
8import tools.refinery.store.adapter.ModelAdapterBuilder;
9
10public interface ModificationBuilder extends ModelAdapterBuilder {
11}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/ModificationStoreAdapter.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/ModificationStoreAdapter.java
new file mode 100644
index 00000000..144c4d05
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/ModificationStoreAdapter.java
@@ -0,0 +1,11 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.modification;
7
8import tools.refinery.store.adapter.ModelStoreAdapter;
9
10public interface ModificationStoreAdapter extends ModelStoreAdapter {
11}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/internal/ModificationAdapterImpl.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/internal/ModificationAdapterImpl.java
new file mode 100644
index 00000000..b2a80d71
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/internal/ModificationAdapterImpl.java
@@ -0,0 +1,62 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.modification.internal;
7
8import tools.refinery.store.adapter.ModelStoreAdapter;
9import tools.refinery.store.dse.modification.ModificationAdapter;
10import tools.refinery.store.model.Interpretation;
11import tools.refinery.store.model.Model;
12import tools.refinery.store.representation.Symbol;
13import tools.refinery.store.tuple.Tuple;
14import tools.refinery.store.tuple.Tuple1;
15
16public class ModificationAdapterImpl implements ModificationAdapter {
17 static final Symbol<Integer> NEXT_ID = Symbol.of("NEXT_ID", 0, Integer.class, 0);
18
19 final ModelStoreAdapter storeAdapter;
20 final Model model;
21 Interpretation<Integer> nodeCountInterpretation;
22
23 ModificationAdapterImpl(ModelStoreAdapter storeAdapter, Model model) {
24 this.storeAdapter = storeAdapter;
25 this.model = model;
26 this.nodeCountInterpretation = model.getInterpretation(NEXT_ID);
27 }
28
29 @Override
30 public Model getModel() {
31 return model;
32 }
33
34 @Override
35 public ModelStoreAdapter getStoreAdapter() {
36 return storeAdapter;
37 }
38
39 @Override
40 public int getModelSize() {
41 return nodeCountInterpretation.get(Tuple.of());
42 }
43
44 @Override
45 public Tuple1 createObject() {
46 var newNodeId = getModelSize();
47 nodeCountInterpretation.put(Tuple.of(), newNodeId + 1);
48 return Tuple.of(newNodeId);
49 }
50
51 @Override
52 public Tuple deleteObject(Tuple tuple) {
53 if (tuple.getSize() != 1) {
54 throw new IllegalArgumentException("Tuple size must be 1");
55 }
56// TODO: implement more efficient deletion
57 if (tuple.get(0) == getModelSize() - 1) {
58 nodeCountInterpretation.put(Tuple.of(), getModelSize() - 1);
59 }
60 return tuple;
61 }
62}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/internal/ModificationBuilderImpl.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/internal/ModificationBuilderImpl.java
new file mode 100644
index 00000000..c4d38d22
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/internal/ModificationBuilderImpl.java
@@ -0,0 +1,29 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.modification.internal;
7
8import tools.refinery.store.adapter.AbstractModelAdapterBuilder;
9import tools.refinery.store.dse.modification.ModificationBuilder;
10import tools.refinery.store.dse.modification.ModificationStoreAdapter;
11import tools.refinery.store.model.ModelStore;
12import tools.refinery.store.model.ModelStoreBuilder;
13import tools.refinery.store.statecoding.StateCoderBuilder;
14
15public class ModificationBuilderImpl extends AbstractModelAdapterBuilder<ModificationStoreAdapter> implements ModificationBuilder {
16
17 @Override
18 protected void doConfigure(ModelStoreBuilder storeBuilder) {
19 storeBuilder.symbols(ModificationAdapterImpl.NEXT_ID);
20 storeBuilder.tryGetAdapter(StateCoderBuilder.class).ifPresent(
21 coderBuilder -> coderBuilder.exclude(ModificationAdapterImpl.NEXT_ID));
22 super.doConfigure(storeBuilder);
23 }
24
25 @Override
26 protected ModificationStoreAdapter doBuild(ModelStore store) {
27 return new ModificationStoreAdapterImpl(store);
28 }
29}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/internal/ModificationStoreAdapterImpl.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/internal/ModificationStoreAdapterImpl.java
new file mode 100644
index 00000000..913cb33f
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/internal/ModificationStoreAdapterImpl.java
@@ -0,0 +1,29 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.modification.internal;
7
8import tools.refinery.store.adapter.ModelAdapter;
9import tools.refinery.store.dse.modification.ModificationStoreAdapter;
10import tools.refinery.store.model.Model;
11import tools.refinery.store.model.ModelStore;
12
13public class ModificationStoreAdapterImpl implements ModificationStoreAdapter {
14 ModelStore store;
15
16 ModificationStoreAdapterImpl(ModelStore store) {
17 this.store = store;
18 }
19
20 @Override
21 public ModelStore getStore() {
22 return store;
23 }
24
25 @Override
26 public ModelAdapter createModelAdapter(Model model) {
27 return null;
28 }
29}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstExplorer.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstExplorer.java
new file mode 100644
index 00000000..72bbbc55
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstExplorer.java
@@ -0,0 +1,164 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.strategy;
7
8import tools.refinery.store.dse.transition.ObjectiveValue;
9import tools.refinery.store.dse.transition.VersionWithObjectiveValue;
10import tools.refinery.store.model.Model;
11
12import java.util.Random;
13
14public class BestFirstExplorer extends BestFirstWorker {
15 final int id;
16 Random random;
17 public BestFirstExplorer(BestFirstStoreManager storeManager, Model model, int id) {
18 super(storeManager, model);
19 this.id = id;
20 this.random = new Random(id);
21 }
22
23 private boolean interrupted = false;
24 public void interrupt() {
25 this.interrupted = true;
26 }
27
28 private boolean shouldRun() {
29 return !interrupted && !hasEnoughSolution();
30 }
31
32 public void explore() {
33 VersionWithObjectiveValue lastVisited = submit().newVersion();
34
35 mainLoop: while (shouldRun()) {
36
37 if (lastVisited == null) {
38 var restored = this.restoreToBest();
39 if(restored != null) {
40 lastVisited = restored;
41 } else {
42 return;
43 }
44 }
45
46 boolean tryActivation = true;
47 while(tryActivation && shouldRun()) {
48 RandomVisitResult randomVisitResult = this.visitRandomUnvisited(random);
49
50 tryActivation &= randomVisitResult.shouldRetry();
51 var newSubmit = randomVisitResult.submitResult();
52 if(newSubmit != null) {
53 if(!newSubmit.include()) {
54 restoreToLast();
55 } else {
56 var newVisit = newSubmit.newVersion();
57 int compareResult = compare(lastVisited,newVisit);
58 if(compareResult >= 0) {
59 lastVisited = newVisit;
60 continue mainLoop;
61 }
62 }
63 }
64 }
65
66 //final ObjectiveComparatorHelper objectiveComparatorHelper = dseAdapter.getObjectiveComparatorHelper();
67
68 /*boolean globalConstraintsAreSatisfied = dseAdapter.checkGlobalConstraints();
69 if (!globalConstraintsAreSatisfied) {
70 // Global constraint is not satisfied in the first state. Terminate.
71 return;
72 }
73
74 final Fitness firstFitness = dseAdapter.getFitness();
75 if (firstFitness.isSatisfiesHardObjectives()) {
76 dseAdapter.newSolution();
77 // First state is a solution. Terminate.
78 if (backTrackIfSolution) {
79 return;
80 }
81 }
82
83 if (maxDepth == 0) {
84 return;
85 }*/
86
87 /*
88 var firstTrajectoryWithFitness = new TrajectoryWithFitness(dseAdapter.getTrajectory(), firstFitness);
89 trajectoriesToExplore.add(firstTrajectoryWithFitness);
90 TrajectoryWithFitness currentTrajectoryWithFitness = null;
91 */
92/*
93 Collection<Activation> activations = dseAdapter.getUntraversedActivations();
94 Iterator<Activation> iterator = activations.iterator();
95
96 while (iterator.hasNext()) {
97 final Activation nextActivation = iterator.next();
98 if (!iterator.hasNext()) {
99 // Last untraversed activation of the state.
100 trajectoriesToExplore.remove(currentTrajectoryWithFitness);
101 }
102
103 // Executing new activation
104 dseAdapter.fireActivation(nextActivation);
105 if (dseAdapter.isCurrentStateAlreadyTraversed()) {
106 // The new state is already visited.
107 dseAdapter.backtrack();
108 } else if (!dseAdapter.checkGlobalConstraints()) {
109 // Global constraint is not satisfied.
110 dseAdapter.backtrack();
111 } else {
112 final Fitness nextFitness = dseAdapter.getFitness();
113 if (nextFitness.isSatisfiesHardObjectives()) {
114 dseAdapter.newSolution();
115 var solutions = dseAdapter.getSolutions().size();
116 if (solutions >= maxSolutions) {
117 return;
118 }
119 // Found a solution.
120 if (backTrackIfSolution) {
121 dseAdapter.backtrack();
122 continue;
123 }
124 }
125 if (dseAdapter.getDepth() >= maxDepth) {
126 // Reached max depth.
127 dseAdapter.backtrack();
128 continue;
129 }
130
131 TrajectoryWithFitness nextTrajectoryWithFitness = new TrajectoryWithFitness(
132 dseAdapter.getTrajectory(), nextFitness);
133 trajectoriesToExplore.add(nextTrajectoryWithFitness);
134
135 int compare = objectiveComparatorHelper.compare(currentTrajectoryWithFitness.fitness,
136 nextTrajectoryWithFitness.fitness);
137 if (compare < 0) {
138 // Better fitness, moving on
139 currentTrajectoryWithFitness = nextTrajectoryWithFitness;
140 continue mainLoop;
141 } else if (compare == 0) {
142 if (onlyBetterFirst) {
143 // Equally good fitness, backtrack
144 dseAdapter.backtrack();
145 } else {
146 // Equally good fitness, moving on
147 currentTrajectoryWithFitness = nextTrajectoryWithFitness;
148 continue mainLoop;
149 }
150 } else {
151 //"Worse fitness
152 currentTrajectoryWithFitness = null;
153 continue mainLoop;
154 }
155 }
156 }
157
158 // State is fully traversed.
159 currentTrajectoryWithFitness = null;
160*/
161 }
162 // Interrupted.
163 }
164}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstStoreManager.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstStoreManager.java
new file mode 100644
index 00000000..693b0903
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstStoreManager.java
@@ -0,0 +1,72 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.strategy;
7
8import tools.refinery.store.dse.transition.DesignSpaceExplorationStoreAdapter;
9import tools.refinery.store.dse.transition.VersionWithObjectiveValue;
10import tools.refinery.store.dse.transition.statespace.ActivationStore;
11import tools.refinery.store.dse.transition.statespace.EquivalenceClassStore;
12import tools.refinery.store.dse.transition.statespace.ObjectivePriorityQueue;
13import tools.refinery.store.dse.transition.statespace.SolutionStore;
14import tools.refinery.store.dse.transition.statespace.internal.ActivationStoreImpl;
15import tools.refinery.store.dse.transition.statespace.internal.FastEquivalenceClassStore;
16import tools.refinery.store.dse.transition.statespace.internal.ObjectivePriorityQueueImpl;
17import tools.refinery.store.dse.transition.statespace.internal.SolutionStoreImpl;
18import tools.refinery.store.map.Version;
19import tools.refinery.store.model.ModelStore;
20import tools.refinery.store.statecoding.StateCoderStoreAdapter;
21
22import java.util.function.Consumer;
23
24public class BestFirstStoreManager {
25 ModelStore modelStore;
26 ObjectivePriorityQueue objectiveStore;
27 ActivationStore activationStore;
28 SolutionStore solutionStore;
29 EquivalenceClassStore equivalenceClassStore;
30
31 public BestFirstStoreManager(ModelStore modelStore) {
32 this.modelStore = modelStore;
33 DesignSpaceExplorationStoreAdapter storeAdapter =
34 modelStore.getAdapter(DesignSpaceExplorationStoreAdapter.class);
35
36 objectiveStore = new ObjectivePriorityQueueImpl(storeAdapter.getObjectives());
37 Consumer<VersionWithObjectiveValue> whenAllActivationsVisited = x -> objectiveStore.remove(x);
38 activationStore = new ActivationStoreImpl(storeAdapter.getTransformations().size(), whenAllActivationsVisited);
39 solutionStore = new SolutionStoreImpl(1);
40 equivalenceClassStore = new FastEquivalenceClassStore(modelStore.getAdapter(StateCoderStoreAdapter.class)) {
41 @Override
42 protected void delegate(VersionWithObjectiveValue version, int[] emptyActivations, boolean accept) {
43 objectiveStore.submit(version);
44 activationStore.markNewAsVisited(version, emptyActivations);
45 if(accept) {
46 solutionStore.submit(version);
47 }
48 }
49 };
50 }
51
52 ObjectivePriorityQueue getObjectiveStore() {
53 return objectiveStore;
54 }
55
56 ActivationStore getActivationStore() {
57 return activationStore;
58 }
59
60 SolutionStore getSolutionStore() {
61 return solutionStore;
62 }
63
64 EquivalenceClassStore getEquivalenceClassStore() {
65 return equivalenceClassStore;
66 }
67
68 public void startExploration(Version initial) {
69 BestFirstExplorer bestFirstExplorer = new BestFirstExplorer(this, modelStore.createModelForState(initial), 1);
70 bestFirstExplorer.explore();
71 }
72}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstWorker.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstWorker.java
new file mode 100644
index 00000000..ea7fe43f
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstWorker.java
@@ -0,0 +1,113 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.strategy;
7
8import tools.refinery.store.dse.transition.DesignSpaceExplorationAdapter;
9import tools.refinery.store.dse.transition.ObjectiveValue;
10import tools.refinery.store.dse.transition.VersionWithObjectiveValue;
11import tools.refinery.store.dse.transition.statespace.internal.ActivationStoreWorker;
12import tools.refinery.store.map.Version;
13import tools.refinery.store.model.Model;
14import tools.refinery.store.statecoding.StateCoderAdapter;
15
16
17import java.util.Random;
18
19public class BestFirstWorker {
20 final BestFirstStoreManager storeManager;
21 final Model model;
22 final ActivationStoreWorker activationStoreWorker;
23 final StateCoderAdapter stateCoderAdapter;
24 final DesignSpaceExplorationAdapter explorationAdapter;
25
26 public BestFirstWorker(BestFirstStoreManager storeManager, Model model) {
27 this.storeManager = storeManager;
28 this.model = model;
29
30 explorationAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
31 stateCoderAdapter = model.getAdapter(StateCoderAdapter.class);
32 activationStoreWorker = new ActivationStoreWorker(storeManager.getActivationStore(),
33 explorationAdapter.getTransformations());
34 }
35
36 private VersionWithObjectiveValue last = null;
37
38 //public boolean isIncluded
39
40 public SubmitResult submit() {
41 if (explorationAdapter.checkExclude()) {
42 last = null;
43 return new SubmitResult(false, false, null, null);
44 }
45
46 Version version = model.commit();
47 ObjectiveValue objectiveValue = explorationAdapter.getObjectiveValue();
48 var res = new VersionWithObjectiveValue(version, objectiveValue);
49 var code = stateCoderAdapter.calculateStateCode();
50 var accepted = explorationAdapter.checkAccept();
51
52 boolean isNew = storeManager.getEquivalenceClassStore().submit(res, code,
53 activationStoreWorker.calculateEmptyActivationSize(), accepted);
54
55 last = new VersionWithObjectiveValue(version, objectiveValue);
56 return new SubmitResult(isNew, accepted, objectiveValue, last);
57 }
58
59 public void restoreToLast() {
60 if (explorationAdapter.getModel().hasUncommittedChanges()) {
61 explorationAdapter.getModel().restore(last.version());
62 }
63 }
64
65 public VersionWithObjectiveValue restoreToBest() {
66 var bestVersion = storeManager.getObjectiveStore().getBest();
67 if (bestVersion != null) {
68 this.model.restore(bestVersion.version());
69 }
70 return bestVersion;
71 }
72
73 public VersionWithObjectiveValue restoreToRandom(Random random) {
74 var randomVersion = storeManager.getObjectiveStore().getRandom(random);
75 last = randomVersion;
76 if (randomVersion != null) {
77 this.model.restore(randomVersion.version());
78 }
79 return randomVersion;
80 }
81
82 public int compare(VersionWithObjectiveValue s1, VersionWithObjectiveValue s2) {
83 return storeManager.getObjectiveStore().getComparator().compare(s1, s2);
84 }
85
86 public boolean stateHasUnvisited() {
87 if (!model.hasUncommittedChanges()) {
88 return storeManager.getActivationStore().hasUnmarkedActivation(last);
89 } else {
90 throw new IllegalStateException("The model has uncommitted changes!");
91 }
92 }
93
94 record RandomVisitResult(SubmitResult submitResult, boolean shouldRetry) {
95 }
96
97 public RandomVisitResult visitRandomUnvisited(Random random) {
98 if (!model.hasUncommittedChanges()) {
99 var visitResult = activationStoreWorker.fireRandomActivation(this.last, random);
100 if (visitResult.successfulVisit()) {
101 return new RandomVisitResult(submit(), visitResult.mayHaveMore());
102 } else {
103 return new RandomVisitResult(null, visitResult.mayHaveMore());
104 }
105 } else {
106 throw new IllegalStateException("The model has uncommitted changes!");
107 }
108 }
109
110 public boolean hasEnoughSolution() {
111 return storeManager.solutionStore.hasEnoughSolution();
112 }
113}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/SubmitResult.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/SubmitResult.java
new file mode 100644
index 00000000..37d548d7
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/SubmitResult.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.strategy;
7
8import tools.refinery.store.dse.transition.ObjectiveValue;
9import tools.refinery.store.dse.transition.VersionWithObjectiveValue;
10import tools.refinery.store.map.Version;
11
12public record SubmitResult(boolean include, boolean accepted, ObjectiveValue objective, VersionWithObjectiveValue newVersion) {
13
14 }
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationAdapter.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationAdapter.java
new file mode 100644
index 00000000..37448309
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationAdapter.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition;
7
8import tools.refinery.store.adapter.ModelAdapter;
9import tools.refinery.store.dse.transition.internal.DesignSpaceExplorationBuilderImpl;
10import tools.refinery.store.map.Version;
11import tools.refinery.store.tuple.Tuple;
12import tools.refinery.store.tuple.Tuple1;
13
14import java.util.Collection;
15import java.util.List;
16
17public interface DesignSpaceExplorationAdapter extends ModelAdapter {
18
19
20
21 @Override
22 DesignSpaceExplorationStoreAdapter getStoreAdapter();
23
24 static DesignSpaceExplorationBuilder builder() {
25 return new DesignSpaceExplorationBuilderImpl();
26 }
27 List<Transformation> getTransformations();
28 boolean checkAccept();
29 boolean checkExclude();
30 ObjectiveValue getObjectiveValue();
31}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationBuilder.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationBuilder.java
new file mode 100644
index 00000000..3855a20a
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationBuilder.java
@@ -0,0 +1,59 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition;
7
8import tools.refinery.store.adapter.ModelAdapterBuilder;
9import tools.refinery.store.dse.transition.objectives.Criterion;
10import tools.refinery.store.dse.transition.objectives.Objective;
11
12import java.util.Collection;
13import java.util.List;
14
15public interface DesignSpaceExplorationBuilder extends ModelAdapterBuilder {
16
17 DesignSpaceExplorationBuilder transformation(TransformationRule transformationRuleDefinition);
18 default DesignSpaceExplorationBuilder transformations(TransformationRule... transformationRuleDefinitions) {
19 return transformations(List.of(transformationRuleDefinitions));
20 }
21
22 default DesignSpaceExplorationBuilder transformations(Collection<? extends TransformationRule> transformationRules) {
23 transformationRules.forEach(this::transformation);
24 return this;
25 }
26
27 DesignSpaceExplorationBuilder accept(Criterion criteria);
28
29 default DesignSpaceExplorationBuilder accept(Criterion... criteria) {
30 return accept(List.of(criteria));
31 }
32
33 default DesignSpaceExplorationBuilder accept(Collection<Criterion> criteria) {
34 criteria.forEach(this::accept);
35 return this;
36 }
37
38 DesignSpaceExplorationBuilder exclude(Criterion criteria);
39
40 default DesignSpaceExplorationBuilder exclude(Criterion... criteria) {
41 return exclude(List.of(criteria));
42 }
43
44 default DesignSpaceExplorationBuilder exclude(Collection<Criterion> criteria) {
45 criteria.forEach(this::exclude);
46 return this;
47 }
48
49 DesignSpaceExplorationBuilder objective(Objective objective);
50
51 default DesignSpaceExplorationBuilder objectives(Objective... objectives) {
52 return objectives(List.of(objectives));
53 }
54
55 default DesignSpaceExplorationBuilder objectives(Collection<? extends Objective> objectives) {
56 objectives.forEach(this::objective);
57 return this;
58 }
59}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationStoreAdapter.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationStoreAdapter.java
new file mode 100644
index 00000000..5c8c7a4d
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationStoreAdapter.java
@@ -0,0 +1,27 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition;
7
8import tools.refinery.store.adapter.ModelStoreAdapter;
9import tools.refinery.store.dse.transition.objectives.Criterion;
10import tools.refinery.store.dse.transition.objectives.Objective;
11import tools.refinery.store.model.Model;
12
13import java.util.List;
14
15public interface DesignSpaceExplorationStoreAdapter extends ModelStoreAdapter
16{
17 @Override
18 DesignSpaceExplorationAdapter createModelAdapter(Model model);
19
20 List<TransformationRule> getTransformations();
21
22 List<Criterion> getAccepts();
23
24 List<Criterion> getExcludes();
25
26 List<Objective> getObjectives();
27}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/ObjectiveValue.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/ObjectiveValue.java
new file mode 100644
index 00000000..89ee61c8
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/ObjectiveValue.java
@@ -0,0 +1,24 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition;
7
8public interface ObjectiveValue {
9 double get(int index);
10 int getSize();
11
12 static ObjectiveValue of(double v1) {
13 return new ObjectiveValues.ObjectiveValue1(v1);
14 }
15
16 static ObjectiveValue of(double v1, double v2) {
17 return new ObjectiveValues.ObjectiveValue2(v1,v2);
18 }
19
20 static ObjectiveValue of(double[] v) {
21 return new ObjectiveValues.ObjectiveValueN(v);
22 }
23
24}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/ObjectiveValues.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/ObjectiveValues.java
new file mode 100644
index 00000000..60913ff3
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/ObjectiveValues.java
@@ -0,0 +1,69 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition;
7
8import java.util.Arrays;
9
10public interface ObjectiveValues {
11 public record ObjectiveValue1(double value0) implements ObjectiveValue {
12 @Override
13 public double get(int index) {
14 if(index == 0) return value0;
15 else throw new IllegalArgumentException("No value at " + index);
16 }
17
18 @Override
19 public int getSize() {
20 return 1;
21 }
22 }
23 public record ObjectiveValue2(double value0, double value1) implements ObjectiveValue {
24 @Override
25 public double get(int index) {
26 if(index == 0) return value0;
27 else if(index == 1) return value1;
28 else throw new IllegalArgumentException("No value at " + index);
29 }
30
31 @Override
32 public int getSize() {
33 return 2;
34 }
35 }
36 public record ObjectiveValueN(double[] values) implements ObjectiveValue {
37 @Override
38 public double get(int index) {
39 return values[index];
40 }
41
42 @Override
43 public int getSize() {
44 return values().length;
45 }
46
47 @Override
48 public boolean equals(Object o) {
49 if (this == o) return true;
50 if (o == null || getClass() != o.getClass()) return false;
51
52 ObjectiveValueN that = (ObjectiveValueN) o;
53
54 return Arrays.equals(values, that.values);
55 }
56
57 @Override
58 public int hashCode() {
59 return Arrays.hashCode(values);
60 }
61
62 @Override
63 public String toString() {
64 return "ObjectiveValueN{" +
65 "values=" + Arrays.toString(values) +
66 '}';
67 }
68 }
69}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/Transformation.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/Transformation.java
new file mode 100644
index 00000000..ab9fda3e
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/Transformation.java
@@ -0,0 +1,44 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition;
7
8import tools.refinery.store.query.resultset.OrderedResultSet;
9import tools.refinery.store.query.resultset.ResultSet;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.function.Consumer;
13
14public class Transformation {
15 private final TransformationRule definition;
16
17 private final OrderedResultSet<Boolean> activations;
18
19 private final Consumer<Tuple> action;
20
21 public Transformation(TransformationRule definition, OrderedResultSet<Boolean> activations, Consumer<Tuple> action) {
22 this.definition = definition;
23 this.activations = activations;
24 this.action = action;
25 }
26
27 public TransformationRule getDefinition() {
28 return definition;
29 }
30
31 public ResultSet<Boolean> getAllActivationsAsResultSet() {
32 return activations;
33 }
34
35 public Tuple getActivation(int index) {
36 return activations.getKey(index);
37 }
38
39 public boolean fireActivation(Tuple activation) {
40 action.accept(activation);
41 //queryEngine.flushChanges();
42 return true;
43 }
44}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/TransformationRule.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/TransformationRule.java
new file mode 100644
index 00000000..d64a3db1
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/TransformationRule.java
@@ -0,0 +1,63 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.model.ModelStoreBuilder;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.ModelQueryBuilder;
12import tools.refinery.store.query.dnf.RelationalQuery;
13import tools.refinery.store.dse.ActionFactory;
14import tools.refinery.store.query.resultset.OrderedResultSet;
15import tools.refinery.store.query.resultset.ResultSet;
16import tools.refinery.store.tuple.Tuple;
17
18import java.util.*;
19
20public class TransformationRule {
21
22 private final String name;
23 private final RelationalQuery precondition;
24 private final ActionFactory actionFactory;
25
26 private Random random;
27 private ModelQueryAdapter queryEngine;
28
29 public TransformationRule(String name, RelationalQuery precondition, ActionFactory actionFactory) {
30 this(name, precondition, actionFactory, new Random());
31 }
32
33 public TransformationRule(String name, RelationalQuery precondition, ActionFactory actionFactory, long seed) {
34 this(name, precondition, actionFactory, new Random(seed));
35 }
36
37 public TransformationRule(String name, RelationalQuery precondition, ActionFactory actionFactory, Random random) {
38 this.name = name;
39 this.precondition = precondition;
40 this.actionFactory = actionFactory;
41 this.random = random;
42 }
43 public void doConfigure(ModelStoreBuilder storeBuilder) {
44 var queryBuilder = storeBuilder.getAdapter(ModelQueryBuilder.class);
45 queryBuilder.query(this.precondition);
46 }
47
48 public Transformation prepare(Model model) {
49 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
50 var activations = new OrderedResultSet<>(queryEngine.getResultSet(precondition));
51 var action = actionFactory.prepare(model);
52 return new Transformation(this,activations,action);
53 }
54
55 public String getName() {
56 return name;
57 }
58
59 public RelationalQuery getPrecondition() {
60 return precondition;
61 }
62
63}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/VersionWithObjectiveValue.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/VersionWithObjectiveValue.java
new file mode 100644
index 00000000..ca28e27f
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/VersionWithObjectiveValue.java
@@ -0,0 +1,11 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition;
7
8import tools.refinery.store.map.Version;
9
10public record VersionWithObjectiveValue(Version version, ObjectiveValue objectiveValue) {
11}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationAdapterImpl.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationAdapterImpl.java
new file mode 100644
index 00000000..e1a29d40
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationAdapterImpl.java
@@ -0,0 +1,90 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.internal;
7
8import tools.refinery.store.dse.transition.DesignSpaceExplorationAdapter;
9import tools.refinery.store.dse.transition.DesignSpaceExplorationStoreAdapter;
10import tools.refinery.store.dse.transition.ObjectiveValue;
11import tools.refinery.store.dse.transition.Transformation;
12import tools.refinery.store.dse.transition.objectives.CriterionCalculator;
13import tools.refinery.store.dse.transition.objectives.ObjectiveCalculator;
14import tools.refinery.store.model.Model;
15
16import java.util.List;
17
18public class DesignSpaceExplorationAdapterImpl implements DesignSpaceExplorationAdapter {
19 final Model model;
20 final DesignSpaceExplorationStoreAdapter designSpaceExplorationStoreAdapter;
21
22 final List<Transformation> transformations;
23 final List<CriterionCalculator> accepts;
24 final List<CriterionCalculator> excludes;
25 final List<ObjectiveCalculator> objectives;
26
27 public DesignSpaceExplorationAdapterImpl(Model model,
28 DesignSpaceExplorationStoreAdapter designSpaceExplorationStoreAdapter,
29 List<Transformation> transformations,
30 List<CriterionCalculator> accepts,
31 List<CriterionCalculator> excludes,
32 List<ObjectiveCalculator> objectives) {
33 this.model = model;
34 this.designSpaceExplorationStoreAdapter = designSpaceExplorationStoreAdapter;
35
36 this.transformations = transformations;
37 this.accepts = accepts;
38 this.excludes = excludes;
39 this.objectives = objectives;
40 }
41
42 @Override
43 public Model getModel() {
44 return model;
45 }
46
47 @Override
48 public DesignSpaceExplorationStoreAdapter getStoreAdapter() {
49 return designSpaceExplorationStoreAdapter;
50 }
51
52 public List<Transformation> getTransformations() {
53 return transformations;
54 }
55
56 @Override
57 public boolean checkAccept() {
58 for (var accept : this.accepts) {
59 if (!accept.isSatisfied()) {
60 return false;
61 }
62 }
63 return true;
64 }
65
66 @Override
67 public boolean checkExclude() {
68 for (var exclude : this.excludes) {
69 if (exclude.isSatisfied()) {
70 return true;
71 }
72 }
73 return false;
74 }
75
76 @Override
77 public ObjectiveValue getObjectiveValue() {
78 if (objectives.size() == 1) {
79 return ObjectiveValue.of(objectives.get(0).getValue());
80 } else if (objectives.size() == 2) {
81 return ObjectiveValue.of(objectives.get(0).getValue(), objectives.get(1).getValue());
82 } else {
83 double[] res = new double[objectives.size()];
84 for (int i = 0; i < objectives.size(); i++) {
85 res[i] = objectives.get(i).getValue();
86 }
87 return ObjectiveValue.of(res);
88 }
89 }
90}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationBuilderImpl.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationBuilderImpl.java
new file mode 100644
index 00000000..4371cc03
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationBuilderImpl.java
@@ -0,0 +1,75 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.internal;
7
8import tools.refinery.store.adapter.AbstractModelAdapterBuilder;
9import tools.refinery.store.dse.transition.DesignSpaceExplorationBuilder;
10import tools.refinery.store.dse.transition.TransformationRule;
11import tools.refinery.store.dse.transition.objectives.Criterion;
12import tools.refinery.store.dse.transition.objectives.Objective;
13import tools.refinery.store.model.ModelStore;
14import tools.refinery.store.model.ModelStoreBuilder;
15
16import java.util.ArrayList;
17import java.util.LinkedHashSet;
18import java.util.List;
19
20public class DesignSpaceExplorationBuilderImpl
21 extends AbstractModelAdapterBuilder<DesignSpaceExplorationStoreAdapterImpl>
22 implements DesignSpaceExplorationBuilder {
23
24 LinkedHashSet<TransformationRule> transformationRuleDefinitions = new LinkedHashSet<>();
25 LinkedHashSet<Criterion> accepts = new LinkedHashSet<>();
26 LinkedHashSet<Criterion> excludes = new LinkedHashSet<>();
27 LinkedHashSet<Objective> objectives = new LinkedHashSet<>();
28
29 @Override
30 public DesignSpaceExplorationBuilder transformation(TransformationRule transformationRuleDefinition) {
31 transformationRuleDefinitions.add(transformationRuleDefinition);
32 return this;
33 }
34
35 @Override
36 public DesignSpaceExplorationBuilder accept(Criterion criteria) {
37 accepts.add(criteria);
38 return this;
39 }
40
41 @Override
42 public DesignSpaceExplorationBuilder exclude(Criterion criteria) {
43 excludes.add(criteria);
44 return this;
45 }
46
47
48 @Override
49 public DesignSpaceExplorationBuilder objective(Objective objective) {
50 objectives.add(objective);
51 return this;
52 }
53
54 @Override
55 protected void doConfigure(ModelStoreBuilder storeBuilder) {
56 transformationRuleDefinitions.forEach(x -> x.doConfigure(storeBuilder));
57 accepts.forEach(x -> x.doConfigure(storeBuilder));
58 excludes.forEach(x -> x.doConfigure(storeBuilder));
59 objectives.forEach(x -> x.doConfigure(storeBuilder));
60
61 super.doConfigure(storeBuilder);
62 }
63
64 @Override
65 protected DesignSpaceExplorationStoreAdapterImpl doBuild(ModelStore store) {
66 List<TransformationRule> transformationRuleDefinitiions1 = new ArrayList<>(transformationRuleDefinitions);
67 List<Criterion> accepts1 = new ArrayList<>(accepts);
68 List<Criterion> excludes1 = new ArrayList<>(excludes);
69 List<Objective> objectives1 = new ArrayList<>(objectives);
70
71 return new DesignSpaceExplorationStoreAdapterImpl(store,
72 transformationRuleDefinitiions1, accepts1,
73 excludes1, objectives1);
74 }
75}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationStoreAdapterImpl.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationStoreAdapterImpl.java
new file mode 100644
index 00000000..3319e148
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationStoreAdapterImpl.java
@@ -0,0 +1,70 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.internal;
7
8import tools.refinery.store.dse.transition.DesignSpaceExplorationStoreAdapter;
9import tools.refinery.store.dse.transition.Transformation;
10import tools.refinery.store.dse.transition.TransformationRule;
11import tools.refinery.store.dse.transition.objectives.Criterion;
12import tools.refinery.store.dse.transition.objectives.CriterionCalculator;
13import tools.refinery.store.dse.transition.objectives.Objective;
14import tools.refinery.store.dse.transition.objectives.ObjectiveCalculator;
15import tools.refinery.store.model.Model;
16import tools.refinery.store.model.ModelStore;
17
18import java.util.List;
19
20public class DesignSpaceExplorationStoreAdapterImpl implements DesignSpaceExplorationStoreAdapter {
21 protected final ModelStore store;
22
23 protected final List<TransformationRule> transformationRuleDefinitions;
24 protected final List<Criterion> accepts;
25 protected final List<Criterion> excludes;
26 protected final List<Objective> objectives;
27
28 public DesignSpaceExplorationStoreAdapterImpl(ModelStore store,
29 List<TransformationRule> transformationRuleDefinitions,
30 List<Criterion> accepts, List<Criterion> excludes,
31 List<Objective> objectives) {
32 this.store = store;
33
34 this.transformationRuleDefinitions = transformationRuleDefinitions;
35 this.accepts = accepts;
36 this.excludes = excludes;
37 this.objectives = objectives;
38 }
39
40 @Override
41 public ModelStore getStore() {
42 return store;
43 }
44
45 @Override
46 public DesignSpaceExplorationAdapterImpl createModelAdapter(Model model) {
47 final List<Transformation> t = this.transformationRuleDefinitions.stream().map(x->x.prepare(model)).toList();
48 final List<CriterionCalculator> a = this.accepts.stream().map(x->x.createCalculator(model)).toList();
49 final List<CriterionCalculator> e = this.excludes.stream().map(x->x.createCalculator(model)).toList();
50 final List<ObjectiveCalculator> o = this.objectives.stream().map(x->x.createCalculator(model)).toList();
51
52 return new DesignSpaceExplorationAdapterImpl(model, this, t, a, e, o);
53 }
54 @Override
55 public List<TransformationRule> getTransformations() {
56 return transformationRuleDefinitions;
57 }
58 @Override
59 public List<Criterion> getAccepts() {
60 return accepts;
61 }
62 @Override
63 public List<Criterion> getExcludes() {
64 return excludes;
65 }
66 @Override
67 public List<Objective> getObjectives() {
68 return objectives;
69 }
70}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Criterion.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Criterion.java
new file mode 100644
index 00000000..66ca6f5e
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Criterion.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.objectives;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.model.ModelStoreBuilder;
10
11public interface Criterion {
12 default void doConfigure(ModelStoreBuilder storeBuilder) {
13 }
14 CriterionCalculator createCalculator(Model model);
15}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/CriterionCalculator.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/CriterionCalculator.java
new file mode 100644
index 00000000..944ffed6
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/CriterionCalculator.java
@@ -0,0 +1,10 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.objectives;
7
8public interface CriterionCalculator {
9 boolean isSatisfied();
10}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Objective.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Objective.java
new file mode 100644
index 00000000..b5924455
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Objective.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.objectives;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.model.ModelStoreBuilder;
10
11public interface Objective {
12 default void doConfigure(ModelStoreBuilder storeBuilder) {
13 }
14 ObjectiveCalculator createCalculator(Model model);
15}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/ObjectiveCalculator.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/ObjectiveCalculator.java
new file mode 100644
index 00000000..f01b8de9
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/ObjectiveCalculator.java
@@ -0,0 +1,10 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.objectives;
7
8public interface ObjectiveCalculator {
9 double getValue();
10}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/QueryCriteria.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/QueryCriteria.java
new file mode 100644
index 00000000..e2260cca
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/QueryCriteria.java
@@ -0,0 +1,44 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.objectives;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.model.ModelStoreBuilder;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.ModelQueryBuilder;
12import tools.refinery.store.query.dnf.AnyQuery;
13
14public class QueryCriteria implements Criterion {
15 protected final boolean acceptIfHasMatch;
16 protected final AnyQuery query;
17
18 /**
19 * Criteria based on the existence of matches evaluated on the model.
20 * @param query The query evaluated on the model.
21 * @param acceptIfHasMatch If true, the criteria satisfied if the query has any match on the model. Otherwise,
22 * the criteria satisfied if the query has no match on the model.
23 */
24 public QueryCriteria(AnyQuery query, boolean acceptIfHasMatch) {
25 this.query = query;
26 this.acceptIfHasMatch = acceptIfHasMatch;
27 }
28
29 @Override
30 public CriterionCalculator createCalculator(Model model) {
31 var resultSet = model.getAdapter(ModelQueryAdapter.class).getResultSet(query);
32 if(acceptIfHasMatch) {
33 return () -> resultSet.size() > 0;
34 } else {
35 return () -> resultSet.size() == 0;
36 }
37 }
38
39 @Override
40 public void doConfigure(ModelStoreBuilder storeBuilder) {
41 Criterion.super.doConfigure(storeBuilder);
42 storeBuilder.getAdapter(ModelQueryBuilder.class).query(query);
43 }
44}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/QueryObjective.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/QueryObjective.java
new file mode 100644
index 00000000..dfddccfc
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/QueryObjective.java
@@ -0,0 +1,44 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.objectives;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.model.ModelStoreBuilder;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.ModelQueryBuilder;
12import tools.refinery.store.query.dnf.FunctionalQuery;
13
14public class QueryObjective implements Objective {
15 protected final FunctionalQuery<? extends Number> objectiveFunction;
16
17 public QueryObjective(FunctionalQuery<? extends Number> objectiveFunction) {
18 this.objectiveFunction = objectiveFunction;
19 }
20
21 @Override
22 public ObjectiveCalculator createCalculator(Model model) {
23 var resultSet = model.getAdapter(ModelQueryAdapter.class).getResultSet(objectiveFunction);
24 return () -> {
25 var cursor = resultSet.getAll();
26 boolean hasElement = cursor.move();
27 if(hasElement) {
28 double result = cursor.getValue().doubleValue();
29 if(cursor.move()) {
30 throw new IllegalStateException("Query providing the objective function has multiple values!");
31 }
32 return result;
33 } else {
34 throw new IllegalStateException("Query providing the objective function has no values!");
35 }
36 };
37 }
38
39 @Override
40 public void doConfigure(ModelStoreBuilder storeBuilder) {
41 Objective.super.doConfigure(storeBuilder);
42 storeBuilder.getAdapter(ModelQueryBuilder.class).query(objectiveFunction);
43 }
44}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/ActivationStore.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/ActivationStore.java
new file mode 100644
index 00000000..52e0611d
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/ActivationStore.java
@@ -0,0 +1,18 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.statespace;
7
8import tools.refinery.store.dse.transition.VersionWithObjectiveValue;
9import tools.refinery.store.map.Version;
10
11import java.util.Random;
12
13public interface ActivationStore {
14 record VisitResult(boolean successfulVisit, boolean mayHaveMore, int transformation, int activation) { }
15 VisitResult markNewAsVisited(VersionWithObjectiveValue to, int[] emptyEntrySizes);
16 boolean hasUnmarkedActivation(VersionWithObjectiveValue version);
17 VisitResult getRandomAndMarkAsVisited(VersionWithObjectiveValue version, Random random);
18}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/EquivalenceClassStore.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/EquivalenceClassStore.java
new file mode 100644
index 00000000..bbe26fe5
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/EquivalenceClassStore.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.statespace;
7
8import tools.refinery.store.dse.transition.VersionWithObjectiveValue;
9import tools.refinery.store.statecoding.StateCoderResult;
10
11public interface EquivalenceClassStore {
12 boolean submit(VersionWithObjectiveValue version, StateCoderResult stateCoderResult, int[] emptyActivations, boolean accept);
13 boolean hasUnresolvedSymmetry();
14 void resolveOneSymmetry();
15 int getNumberOfUnresolvedSymmetries();
16}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/ObjectivePriorityQueue.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/ObjectivePriorityQueue.java
new file mode 100644
index 00000000..df72c343
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/ObjectivePriorityQueue.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.statespace;
7
8import tools.refinery.store.dse.transition.VersionWithObjectiveValue;
9import tools.refinery.store.map.Version;
10
11import java.util.Comparator;
12import java.util.Random;
13
14public interface ObjectivePriorityQueue {
15 Comparator<VersionWithObjectiveValue> getComparator();
16 void submit(VersionWithObjectiveValue versionWithObjectiveValue);
17 void remove(VersionWithObjectiveValue versionWithObjectiveValue);
18 int getSize();
19 VersionWithObjectiveValue getBest();
20 VersionWithObjectiveValue getRandom(Random random);
21}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/SolutionStore.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/SolutionStore.java
new file mode 100644
index 00000000..d1bfaa79
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/SolutionStore.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.statespace;
7
8import tools.refinery.store.dse.transition.VersionWithObjectiveValue;
9
10import java.util.List;
11import java.util.concurrent.Future;
12
13public interface SolutionStore {
14 boolean submit(VersionWithObjectiveValue version);
15 List<VersionWithObjectiveValue> getSolutions();
16 boolean hasEnoughSolution();
17}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/AbstractEquivalenceClassStore.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/AbstractEquivalenceClassStore.java
new file mode 100644
index 00000000..8466a0f3
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/AbstractEquivalenceClassStore.java
@@ -0,0 +1,47 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.statespace.internal;
7
8import tools.refinery.store.dse.transition.VersionWithObjectiveValue;
9import tools.refinery.store.dse.transition.statespace.EquivalenceClassStore;
10import tools.refinery.store.statecoding.StateCoderResult;
11import tools.refinery.store.statecoding.StateCoderStoreAdapter;
12
13public abstract class AbstractEquivalenceClassStore implements EquivalenceClassStore {
14 protected final StateCoderStoreAdapter stateCoderStoreAdapter;
15 AbstractEquivalenceClassStore(StateCoderStoreAdapter stateCoderStoreAdapter) {
16 this.stateCoderStoreAdapter = stateCoderStoreAdapter;
17 }
18
19 protected int numberOfUnresolvedSymmetries = 0;
20
21 protected abstract void delegate(VersionWithObjectiveValue version, int[] emptyActivations, boolean accept);
22 protected abstract boolean tryToAdd(StateCoderResult stateCoderResult, VersionWithObjectiveValue newVersion,
23 int[] emptyActivations, boolean accept);
24
25 @Override
26 public synchronized boolean submit(VersionWithObjectiveValue version, StateCoderResult stateCoderResult,
27 int[] emptyActivations, boolean accept) {
28 boolean hasNewVersion = tryToAdd(stateCoderResult, version, emptyActivations, accept);
29 if (hasNewVersion) {
30 delegate(version, emptyActivations, accept);
31 return true;
32 } else {
33 numberOfUnresolvedSymmetries++;
34 return false;
35 }
36 }
37
38 @Override
39 public boolean hasUnresolvedSymmetry() {
40 return numberOfUnresolvedSymmetries > 0;
41 }
42
43 @Override
44 public int getNumberOfUnresolvedSymmetries() {
45 return numberOfUnresolvedSymmetries;
46 }
47}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreBitVectorEntry.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreBitVectorEntry.java
new file mode 100644
index 00000000..ba243d7d
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreBitVectorEntry.java
@@ -0,0 +1,46 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.statespace.internal;
7
8public class ActivationStoreBitVectorEntry extends ActivationStoreEntry {
9 final int[] selected;
10
11 ActivationStoreBitVectorEntry(int numberOfActivations) {
12 super(numberOfActivations);
13 this.selected = new int[(numberOfActivations / Integer.SIZE) + 1];
14 }
15
16 @Override
17 public int getNumberOfUnvisitedActivations() {
18 int visited = 0;
19 for (int i : selected) {
20 visited += Integer.bitCount(i);
21 }
22 return visited;
23 }
24
25 private static final int ELEMENT_POSITION = 5; // size of Integer.SIZE
26 private static final int ELEMENT_BITMASK = (1<<ELEMENT_POSITION)-1;
27 @Override
28 public int getAndAddActivationAfter(int index) {
29 int position = index;
30 do {
31 final int selectedElement = position >> ELEMENT_POSITION;
32 final int selectedBit = position & ELEMENT_BITMASK;
33 if((selected[selectedElement] & selectedBit) == 0) {
34 selected[selectedElement] |= selectedBit;
35 return position;
36 } else {
37 if(position < this.numberOfActivations) {
38 position++;
39 } else {
40 position = 0;
41 }
42 }
43 } while(position != index);
44 throw new IllegalArgumentException("There is are no unvisited activations!");
45 }
46}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreEntry.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreEntry.java
new file mode 100644
index 00000000..f69b234c
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreEntry.java
@@ -0,0 +1,32 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.statespace.internal;
7
8public abstract class ActivationStoreEntry {
9 protected final int numberOfActivations;
10
11 ActivationStoreEntry(int numberOfActivations) {
12 this.numberOfActivations = numberOfActivations;
13 }
14
15 public int getNumberOfVisitedActivations() {
16 return numberOfActivations;
17 }
18
19 public abstract int getNumberOfUnvisitedActivations();
20 public abstract int getAndAddActivationAfter(int index);
21
22 // public abstract boolean contains(int activation)
23 // public abstract boolean add(int activation)
24
25 public static ActivationStoreEntry create(int size) {
26 if(size <= Integer.SIZE*6) {
27 return new ActivationStoreBitVectorEntry(size);
28 } else {
29 return new ActivationStoreListEntry(size);
30 }
31 }
32}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreImpl.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreImpl.java
new file mode 100644
index 00000000..3e59b21a
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreImpl.java
@@ -0,0 +1,131 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.statespace.internal;
7
8import tools.refinery.store.dse.transition.VersionWithObjectiveValue;
9import tools.refinery.store.dse.transition.statespace.ActivationStore;
10
11import java.util.*;
12import java.util.function.Consumer;
13
14public class ActivationStoreImpl implements ActivationStore {
15 final int numberOfTransformations;
16 final Consumer<VersionWithObjectiveValue> actionWhenAllActivationVisited;
17 final Map<VersionWithObjectiveValue, List<ActivationStoreEntry>> versionToActivations;
18
19 public ActivationStoreImpl(final int numberOfTransformations,
20 Consumer<VersionWithObjectiveValue> actionWhenAllActivationVisited) {
21 this.numberOfTransformations = numberOfTransformations;
22 this.actionWhenAllActivationVisited = actionWhenAllActivationVisited;
23 versionToActivations = new HashMap<>();
24 }
25
26 public synchronized VisitResult markNewAsVisited(VersionWithObjectiveValue to, int[] emptyEntrySizes) {
27 boolean[] successful = new boolean[]{false};
28 var entries = versionToActivations.computeIfAbsent(to, x -> {
29 successful[0] = true;
30 List<ActivationStoreEntry> result = new ArrayList<>(emptyEntrySizes.length);
31 for(int emptyEntrySize : emptyEntrySizes) {
32 result.add(ActivationStoreListEntry.create(emptyEntrySize));
33 }
34 return result;
35 });
36 boolean hasMore = false;
37 for (var entry : entries) {
38 if (entry.getNumberOfUnvisitedActivations() > 0) {
39 hasMore = true;
40 break;
41 }
42 }
43 if(!hasMore) {
44 actionWhenAllActivationVisited.accept(to);
45 }
46 return new VisitResult(successful[0], hasMore, -1, -1);
47 }
48
49 public synchronized VisitResult visitActivation(VersionWithObjectiveValue from, int transformationIndex, int activationIndex) {
50 var entries = versionToActivations.get(from);
51 var entry = entries.get(transformationIndex);
52 final int unvisited = entry.getNumberOfUnvisitedActivations();
53
54 final boolean successfulVisit = unvisited > 0;
55 final boolean hasMoreInActivation = unvisited > 1;
56 final boolean hasMore;
57 final int transformation;
58 final int activation;
59
60 if (successfulVisit) {
61 transformation = transformationIndex;
62 activation = entry.getAndAddActivationAfter(activationIndex);
63
64 } else {
65 transformation = -1;
66 activation = -1;
67 }
68
69 if(hasMoreInActivation) {
70 boolean hasMoreInOtherTransformation = false;
71 for (var e : entries) {
72 if (e != entry && e.getNumberOfVisitedActivations() > 0) {
73 hasMoreInOtherTransformation = true;
74 break;
75 }
76 }
77 hasMore = hasMoreInOtherTransformation;
78 } else {
79 hasMore = true;
80 }
81
82 if(!hasMore) {
83 actionWhenAllActivationVisited.accept(from);
84 }
85
86 return new VisitResult(false, hasMore, transformation, activation);
87 }
88
89 @Override
90 public synchronized boolean hasUnmarkedActivation(VersionWithObjectiveValue version) {
91 var entries = versionToActivations.get(version);
92 boolean hasMore = false;
93 for (var entry : entries) {
94 if (entry.getNumberOfUnvisitedActivations() > 0) {
95 hasMore = true;
96 break;
97 }
98 }
99 return hasMore;
100 }
101
102 @Override
103 public synchronized VisitResult getRandomAndMarkAsVisited(VersionWithObjectiveValue version, Random random) {
104 var entries = versionToActivations.get(version);
105
106 int sum1 = 0;
107 for (var entry : entries) {
108 sum1 += entry.getNumberOfUnvisitedActivations();
109 }
110
111 int selected = random.nextInt(sum1);
112 int sum2 = 0;
113 int transformation = 0;
114 int activation = -1;
115 for (; transformation < entries.size(); transformation++) {
116 var entry = entries.get(transformation);
117 int unvisited = entry.getNumberOfUnvisitedActivations();
118 if (selected < sum2 + unvisited) {
119 activation = sum2 + unvisited - selected;
120 break;
121 } else {
122 sum2 += unvisited;
123 }
124 }
125 if (activation == -1) {
126 throw new IllegalArgumentException("no unvisited");
127 }
128
129 return this.visitActivation(version, transformation, activation);
130 }
131}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreListEntry.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreListEntry.java
new file mode 100644
index 00000000..e560f8c5
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreListEntry.java
@@ -0,0 +1,95 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.statespace.internal;
7
8import org.eclipse.collections.api.factory.primitive.IntLists;
9import org.eclipse.collections.api.list.primitive.MutableIntList;
10
11public class ActivationStoreListEntry extends ActivationStoreEntry {
12 private final MutableIntList visitedActivations = IntLists.mutable.empty();
13
14 ActivationStoreListEntry(int numberOfActivations) {
15 super(numberOfActivations);
16 }
17
18 @Override
19 public int getNumberOfUnvisitedActivations() {
20 return this.numberOfActivations - visitedActivations.size();
21 }
22
23 @Override
24 public int getAndAddActivationAfter(int index) {
25 // If it is empty, just add it.
26 if(this.visitedActivations.isEmpty()) {
27 this.visitedActivations.add(index);
28 return index;
29 }
30
31 int position = getPosition(index);
32
33 // If the index is not in the position, one can insert it
34 if(this.visitedActivations.get(position) != index) {
35 this.visitedActivations.addAtIndex(position,index);
36 return index;
37 }
38
39 // Otherwise, get the next empty space between two elements
40 while(position + 2 < this.visitedActivations.size()) {
41 position++;
42 if(this.visitedActivations.get(position+1)-this.visitedActivations.get(position) > 1) {
43 this.visitedActivations.addAtIndex(position+1, this.visitedActivations.get(position+1)+1);
44 }
45 }
46
47 // Otherwise, try to add to the last space
48 int last = this.visitedActivations.get(this.visitedActivations.size()-1);
49 if(last<this.numberOfActivations) {
50 this.visitedActivations.add(last+1);
51 return last+1;
52 }
53
54 // Otherwise, try to put to the beginning
55 if(this.visitedActivations.get(0) > 0) {
56 this.visitedActivations.add(0);
57 return 0;
58 }
59
60 // Otherwise, get the next empty space between two elements
61 position = 0;
62 while(position + 2 < this.visitedActivations.size()) {
63 position++;
64 if(this.visitedActivations.get(position+1)-this.visitedActivations.get(position) > 1) {
65 this.visitedActivations.addAtIndex(position+1, this.visitedActivations.get(position+1)+1);
66 }
67 }
68
69 throw new IllegalArgumentException("There is are no unvisited activations!");
70 }
71
72 /**
73 * Returns the position of an index in the {@code visitedActivations}. If the collection contains the index, in
74 * returns its position, otherwise, it returns the position where the index need to be put.
75 *
76 * @param index Index of an activation.
77 * @return The position of the index.
78 */
79 private int getPosition(int index) {
80 int left = 0;
81 int right = this.visitedActivations.size() - 1;
82 while (left <= right) {
83 final int middle = (right - left) / 2 + left;
84 final int middleElement = visitedActivations.get(middle);
85 if(middleElement == index) {
86 return middle;
87 } else if(middleElement < index) {
88 left = middle +1;
89 } else{
90 right = middle-1;
91 }
92 }
93 return right+1;
94 }
95}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreWorker.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreWorker.java
new file mode 100644
index 00000000..e05f5122
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreWorker.java
@@ -0,0 +1,56 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.statespace.internal;
7
8import tools.refinery.store.dse.transition.Transformation;
9import tools.refinery.store.dse.transition.VersionWithObjectiveValue;
10import tools.refinery.store.dse.transition.statespace.ActivationStore;
11import tools.refinery.store.map.Version;
12
13import java.util.List;
14import java.util.Random;
15
16public class ActivationStoreWorker {
17 final ActivationStore store;
18 final List<Transformation> transformations;
19
20 public ActivationStoreWorker(ActivationStore store, List<Transformation> transformations) {
21 this.store = store;
22 this.transformations = transformations;
23 }
24
25 public int[] calculateEmptyActivationSize() {
26 int[] result = new int[transformations.size()];
27 for (int i = 0; i < result.length; i++) {
28 result[i] = transformations.get(i).getAllActivationsAsResultSet().size();
29 }
30 return result;
31 }
32
33
34 public ActivationStore.VisitResult fireRandomActivation(VersionWithObjectiveValue thisVersion, Random random) {
35 var result = store.getRandomAndMarkAsVisited(thisVersion, random);
36 if(result.successfulVisit()) {
37 int selectedTransformation = result.transformation();
38 int selectedActivation = result.activation();
39
40 Transformation transformation = transformations.get(selectedTransformation);
41 var tuple = transformation.getActivation(selectedActivation);
42
43 boolean success = transformation.fireActivation(tuple);
44 if(success) {
45 return result;
46 } else {
47 return new ActivationStore.VisitResult(
48 false,
49 result.mayHaveMore(),
50 selectedActivation,
51 selectedActivation);
52 }
53 }
54 return result;
55 }
56}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/CompleteEquivalenceClassStore.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/CompleteEquivalenceClassStore.java
new file mode 100644
index 00000000..20a026b6
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/CompleteEquivalenceClassStore.java
@@ -0,0 +1,104 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.statespace.internal;
7
8import org.eclipse.collections.api.factory.primitive.IntObjectMaps;
9import org.eclipse.collections.api.map.primitive.MutableIntObjectMap;
10import tools.refinery.store.dse.transition.VersionWithObjectiveValue;
11import tools.refinery.store.dse.transition.statespace.EquivalenceClassStore;
12import tools.refinery.store.statecoding.StateCoderResult;
13import tools.refinery.store.statecoding.StateCoderStoreAdapter;
14import tools.refinery.store.statecoding.StateEquivalenceChecker;
15
16import java.util.ArrayList;
17
18public abstract class CompleteEquivalenceClassStore extends AbstractEquivalenceClassStore implements EquivalenceClassStore {
19
20 static class SymmetryStoreArray extends ArrayList<VersionWithObjectiveValue> {
21 final int[] activationSizes;
22 final boolean accept;
23
24 SymmetryStoreArray(int[] activationSizes, boolean accept) {
25 super();
26 this.activationSizes = activationSizes;
27 this.accept = accept;
28 }
29 }
30
31 private final MutableIntObjectMap<Object> modelCode2Versions = IntObjectMaps.mutable.empty();
32
33 protected CompleteEquivalenceClassStore(StateCoderStoreAdapter stateCoderStoreAdapter) {
34 super(stateCoderStoreAdapter);
35 }
36
37 @Override
38 protected boolean tryToAdd(StateCoderResult stateCoderResult, VersionWithObjectiveValue newVersion,
39 int[] emptyActivations, boolean accept) {
40 int modelCode = stateCoderResult.modelCode();
41 Object old = modelCode2Versions.updateValue(
42 modelCode,
43 () -> newVersion,
44 x -> {
45 if (x instanceof SymmetryStoreArray array) {
46 if(array.accept != accept || array.activationSizes != emptyActivations) {
47 this.delegate(newVersion,emptyActivations,accept);
48 return x;
49 }
50 array.add(newVersion);
51 return array;
52 } else {
53 SymmetryStoreArray result = new SymmetryStoreArray(emptyActivations, accept);
54 result.add((VersionWithObjectiveValue) x);
55 result.add(newVersion);
56 return result;
57 }
58 });
59 return old == null;
60 }
61
62 @Override
63 public void resolveOneSymmetry() {
64 var unresolvedSimilarity = getOneUnresolvedSymmetry();
65 if (unresolvedSimilarity == null) {
66 return;
67 }
68 var outcome = this.stateCoderStoreAdapter.checkEquivalence(unresolvedSimilarity.get(0).version(),
69 unresolvedSimilarity.get(1).version());
70 if (outcome != StateEquivalenceChecker.EquivalenceResult.ISOMORPHIC) {
71 delegate(unresolvedSimilarity.get(1), unresolvedSimilarity.activationSizes, unresolvedSimilarity.accept);
72 }
73 }
74
75 //record UnresolvedSymmetryResult
76
77 private synchronized SymmetryStoreArray getOneUnresolvedSymmetry() {
78 if (numberOfUnresolvedSymmetries <= 0) {
79 return null;
80 }
81
82 for (var entry : modelCode2Versions.keyValuesView()) {
83 int hash = entry.getOne();
84 var value = entry.getTwo();
85 if (value instanceof SymmetryStoreArray array) {
86 int size = array.size();
87 var representative = array.get(0);
88 var similar = array.get(size - 1);
89 array.remove(size - 1);
90
91 if (size <= 2) {
92 modelCode2Versions.put(hash, representative);
93 }
94
95 var result = new SymmetryStoreArray(array.activationSizes, array.accept);
96 result.add(representative);
97 result.add(similar);
98 return result;
99 }
100 }
101
102 return null;
103 }
104}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/FastEquivalenceClassStore.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/FastEquivalenceClassStore.java
new file mode 100644
index 00000000..6ee8e196
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/FastEquivalenceClassStore.java
@@ -0,0 +1,32 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.statespace.internal;
7
8import org.eclipse.collections.api.factory.primitive.IntSets;
9import org.eclipse.collections.api.set.primitive.MutableIntSet;
10import tools.refinery.store.dse.transition.VersionWithObjectiveValue;
11import tools.refinery.store.dse.transition.statespace.EquivalenceClassStore;
12import tools.refinery.store.statecoding.StateCoderResult;
13import tools.refinery.store.statecoding.StateCoderStoreAdapter;
14
15public abstract class FastEquivalenceClassStore extends AbstractEquivalenceClassStore implements EquivalenceClassStore {
16
17 private final MutableIntSet codes = IntSets.mutable.empty();
18
19 public FastEquivalenceClassStore(StateCoderStoreAdapter stateCoderStoreAdapter) {
20 super(stateCoderStoreAdapter);
21 }
22
23 @Override
24 protected boolean tryToAdd(StateCoderResult stateCoderResult, VersionWithObjectiveValue newVersion, int[] emptyActivations, boolean accept) {
25 return this.codes.add(stateCoderResult.modelCode());
26 }
27
28 @Override
29 public void resolveOneSymmetry() {
30 throw new IllegalArgumentException("This equivalence storage is not prepared to resolve symmetries!");
31 }
32}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ObjectivePriorityQueueImpl.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ObjectivePriorityQueueImpl.java
new file mode 100644
index 00000000..249b22da
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ObjectivePriorityQueueImpl.java
@@ -0,0 +1,74 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.statespace.internal;
7
8import tools.refinery.store.dse.transition.ObjectiveValues;
9import tools.refinery.store.dse.transition.VersionWithObjectiveValue;
10import tools.refinery.store.dse.transition.objectives.Objective;
11import tools.refinery.store.dse.transition.statespace.ObjectivePriorityQueue;
12import tools.refinery.store.map.Version;
13
14import java.util.Comparator;
15import java.util.List;
16import java.util.PriorityQueue;
17import java.util.Random;
18
19public class ObjectivePriorityQueueImpl implements ObjectivePriorityQueue {
20 public static final Comparator<VersionWithObjectiveValue> c1 = (o1, o2) -> Double.compare(
21 ((ObjectiveValues.ObjectiveValue1) o1.objectiveValue()).value0(),
22 ((ObjectiveValues.ObjectiveValue1) o2.objectiveValue()).value0());
23 // TODO: support multi objective!
24 final PriorityQueue<VersionWithObjectiveValue> priorityQueue;
25
26 public ObjectivePriorityQueueImpl(List<Objective> objectives) {
27
28 if(objectives.size() == 1) {
29 this.priorityQueue = new PriorityQueue<>(c1);
30 } else {
31 throw new UnsupportedOperationException("Only single objective comparator is implemented currently!");
32 }
33 }
34 @Override
35 public Comparator<VersionWithObjectiveValue> getComparator() {
36 return c1;
37 }
38
39 @Override
40 public void submit(VersionWithObjectiveValue versionWithObjectiveValue) {
41 priorityQueue.add(versionWithObjectiveValue);
42 }
43
44 @Override
45 public void remove(VersionWithObjectiveValue versionWithObjectiveValue) {
46 priorityQueue.remove(versionWithObjectiveValue);
47 }
48
49 @Override
50 public int getSize() {
51 return priorityQueue.size();
52 }
53
54 @Override
55 public VersionWithObjectiveValue getBest() {
56 var best = priorityQueue.peek();
57 if (best != null) {
58 return best;
59 } else {
60 throw new IllegalArgumentException("The objective store is empty!");
61 }
62 }
63
64 @Override
65 public VersionWithObjectiveValue getRandom(Random random) {
66 int randomPosition = random.nextInt(getSize());
67 for (VersionWithObjectiveValue entry : this.priorityQueue) {
68 if (randomPosition-- == 0) {
69 return entry;
70 }
71 }
72 throw new IllegalStateException("The priority queue is inconsistent!");
73 }
74}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/SolutionStoreImpl.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/SolutionStoreImpl.java
new file mode 100644
index 00000000..cc48864f
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/SolutionStoreImpl.java
@@ -0,0 +1,53 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.statespace.internal;
7
8import tools.refinery.store.dse.transition.VersionWithObjectiveValue;
9import tools.refinery.store.dse.transition.statespace.SolutionStore;
10
11import java.util.ArrayList;
12import java.util.List;
13import java.util.SortedSet;
14import java.util.TreeSet;
15
16public class SolutionStoreImpl implements SolutionStore {
17 final int maxNumberSolutions;
18 public static final int UNLIMITED = -1;
19 final SortedSet<VersionWithObjectiveValue> solutions;
20
21 public SolutionStoreImpl(int maxNumberSolutions) {
22 this.maxNumberSolutions = maxNumberSolutions;
23 solutions = new TreeSet<>(ObjectivePriorityQueueImpl.c1);
24 }
25
26
27 @Override
28 public synchronized boolean submit(VersionWithObjectiveValue version) {
29 boolean removeLast = hasEnoughSolution();
30 solutions.add(version);
31 if(removeLast) {
32 var last = solutions.last();
33 solutions.remove(last);
34 return last != version;
35 } else {
36 return true;
37 }
38 }
39
40 @Override
41 public List<VersionWithObjectiveValue> getSolutions() {
42 return new ArrayList<>(solutions);
43 }
44
45 @Override
46 public boolean hasEnoughSolution() {
47 if (maxNumberSolutions == UNLIMITED) {
48 return false;
49 } else {
50 return solutions.size() >= maxNumberSolutions;
51 }
52 }
53}
diff --git a/subprojects/store-dse/src/test/java/tools/refinery/store/dse/CRAExamplesTest.java b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/CRAExamplesTest.java
new file mode 100644
index 00000000..36517709
--- /dev/null
+++ b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/CRAExamplesTest.java
@@ -0,0 +1,277 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse;
7
8import tools.refinery.store.dse.transition.TransformationRule;
9import tools.refinery.store.query.dnf.Query;
10import tools.refinery.store.query.dnf.RelationalQuery;
11import tools.refinery.store.query.view.AnySymbolView;
12import tools.refinery.store.query.view.KeyOnlyView;
13import tools.refinery.store.representation.Symbol;
14import tools.refinery.store.tuple.Tuple;
15
16import java.util.List;
17
18import static tools.refinery.store.query.literal.Literals.not;
19
20class CRAExamplesTest {
21
22 private static final Symbol<String> name = Symbol.of("Name", 1, String.class);
23
24// private static final Symbol<Boolean> classModel = Symbol.of("ClassModel", 1);
25 private static final Symbol<Boolean> classElement = Symbol.of("ClassElement", 1);
26// private static final Symbol<Boolean> feature = Symbol.of("Feature", 1);
27 private static final Symbol<Boolean> attribute = Symbol.of("Attribute", 1);
28 private static final Symbol<Boolean> method = Symbol.of("Method", 1);
29
30// private static final Symbol<Boolean> isEncapsulatedBy = Symbol.of("IsEncapsulatedBy", 2);
31 private static final Symbol<Boolean> encapsulates = Symbol.of("Encapsulates", 2);
32 private static final Symbol<Boolean> dataDependency = Symbol.of("DataDependency", 2);
33 private static final Symbol<Boolean> functionalDependency = Symbol.of("FunctionalDependency", 2);
34
35 private static final Symbol<Boolean> features = Symbol.of("Features", 2);
36 private static final Symbol<Boolean> classes = Symbol.of("Classes", 2);
37
38// private static final AnySymbolView classModelView = new KeyOnlyView<>(classModel);
39 private static final AnySymbolView classElementView = new KeyOnlyView<>(classElement);
40// private static final AnySymbolView featureView = new KeyOnlyView<>(feature);
41 private static final AnySymbolView attributeView = new KeyOnlyView<>(attribute);
42 private static final AnySymbolView methodView = new KeyOnlyView<>(method);
43// private static final AnySymbolView isEncapsulatedByView = new KeyOnlyView<>(isEncapsulatedBy);
44 private static final AnySymbolView encapsulatesView = new KeyOnlyView<>(encapsulates);
45 private static final AnySymbolView dataDependencyView = new KeyOnlyView<>(dataDependency);
46 private static final AnySymbolView functionalDependencyView = new KeyOnlyView<>(functionalDependency);
47 private static final AnySymbolView featuresView = new KeyOnlyView<>(features);
48 private static final AnySymbolView classesView = new KeyOnlyView<>(classes);
49
50 /*Example Transformation rules*/
51 private static final RelationalQuery feature = Query.of("Feature",
52 (builder, f) -> builder.clause(
53 attributeView.call(f))
54 .clause(
55 methodView.call(f))
56 );
57
58 private static final RelationalQuery assignFeaturePreconditionHelper = Query.of("AssignFeaturePreconditionHelper",
59 (builder, c, f) -> builder.clause(
60 classElementView.call(c),
61// classesView.call(model, c),
62 encapsulatesView.call(c, f)
63 ));
64
65 private static final RelationalQuery assignFeaturePrecondition = Query.of("AssignFeaturePrecondition",
66 (builder, f, c1) -> builder.clause((c2) -> List.of(
67// classModelView.call(model),
68 feature.call(f),
69 classElementView.call(c1),
70// featuresView.call(model, f),
71 not(assignFeaturePreconditionHelper.call(c2, f)),
72 not(encapsulatesView.call(c1, f))
73 )));
74
75 private static final RelationalQuery deleteEmptyClassPrecondition = Query.of("DeleteEmptyClassPrecondition",
76 (builder, c) -> builder.clause((f) -> List.of(
77// classModelView.call(model),
78 classElementView.call(c),
79// featuresView.call(model, f),
80 not(encapsulatesView.call(c, f))
81 )));
82
83 private static final RelationalQuery createClassPreconditionHelper = Query.of("CreateClassPreconditionHelper",
84 (builder, f, c) -> builder.clause(
85 classElementView.call(c),
86// classesView.call(model, c),
87 encapsulatesView.call(c, f)
88 ));
89
90 private static final RelationalQuery createClassPrecondition = Query.of("CreateClassPrecondition",
91 (builder, f) -> builder.clause((c) -> List.of(
92// classModelView.call(model),
93 feature.call(f),
94 not(createClassPreconditionHelper.call(f, c))
95 )));
96
97 private static final RelationalQuery moveFeaturePrecondition = Query.of("MoveFeature",
98 (builder, c1, c2, f) -> builder.clause(
99// classModelView.call(model),
100 classElementView.call(c1),
101 classElementView.call(c2),
102 c1.notEquivalent(c2),
103 feature.call(f),
104// classesView.call(model, c1),
105// classesView.call(model, c2),
106// featuresView.call(model, f),
107 encapsulatesView.call(c1, f)
108 ));
109
110 private static final TransformationRule assignFeatureRule = new TransformationRule("AssignFeature",
111 assignFeaturePrecondition,
112 (model) -> {
113// var isEncapsulatedByInterpretation = model.getInterpretation(isEncapsulatedBy);
114 var encapsulatesInterpretation = model.getInterpretation(encapsulates);
115 return ((Tuple activation) -> {
116 var feature = activation.get(0);
117 var classElement = activation.get(1);
118
119// isEncapsulatedByInterpretation.put(Tuple.of(feature, classElement), true);
120 encapsulatesInterpretation.put(Tuple.of(classElement, feature), true);
121 });
122 });
123
124// private static final TransformationRule deleteEmptyClassRule = new TransformationRule("DeleteEmptyClass",
125// deleteEmptyClassPrecondition,
126// (model) -> {
127//// var classesInterpretation = model.getInterpretation(classes);
128// var classElementInterpretation = model.getInterpretation(classElement);
129// return ((Tuple activation) -> {
130// // TODO: can we move dseAdapter outside?
131// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
132//// var modelElement = activation.get(0);
133// var classElement = activation.get(0);
134//
135//// classesInterpretation.put(Tuple.of(modelElement, classElement), false);
136// classElementInterpretation.put(Tuple.of(classElement), false);
137// dseAdapter.deleteObject(Tuple.of(classElement));
138// });
139// });
140
141// private static final TransformationRule createClassRule = new TransformationRule("CreateClass",
142// createClassPrecondition,
143// (model) -> {
144// var classElementInterpretation = model.getInterpretation(classElement);
145//// var classesInterpretation = model.getInterpretation(classes);
146// var encapsulatesInterpretation = model.getInterpretation(encapsulates);
147// return ((Tuple activation) -> {
148// // TODO: can we move dseAdapter outside?
149// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
150//// var modelElement = activation.get(0);
151// var feature = activation.get(0);
152//
153// var newClassElement = dseAdapter.createObject();
154// var newClassElementId = newClassElement.get(0);
155// classElementInterpretation.put(newClassElement, true);
156//// classesInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
157// encapsulatesInterpretation.put(Tuple.of(newClassElementId, feature), true);
158// });
159// });
160
161 private static final TransformationRule moveFeatureRule = new TransformationRule("MoveFeature",
162 moveFeaturePrecondition,
163 (model) -> {
164 var encapsulatesInterpretation = model.getInterpretation(encapsulates);
165 return ((Tuple activation) -> {
166 var classElement1 = activation.get(0);
167 var classElement2 = activation.get(1);
168 var feature = activation.get(2);
169
170 encapsulatesInterpretation.put(Tuple.of(classElement1, feature), false);
171 encapsulatesInterpretation.put(Tuple.of(classElement2, feature), true);
172 });
173 });
174
175// @Test
176// @Disabled("This test is only for debugging purposes")
177// void craTest() {
178// var store = ModelStore.builder()
179// .symbols(classElement, encapsulates, classes, features, attribute, method, dataDependency,
180// functionalDependency, name)
181// .with(ViatraModelQueryAdapter.builder()
182// .queries(feature, assignFeaturePreconditionHelper, assignFeaturePrecondition,
183// deleteEmptyClassPrecondition, createClassPreconditionHelper, createClassPrecondition,
184// moveFeaturePrecondition))
185// .with(ModelVisualizerAdapter.builder()
186// .withOutputpath("test_output")
187// .withFormat(FileFormat.DOT)
188// .withFormat(FileFormat.SVG)
189// .saveStates()
190// .saveDesignSpace()
191// )
192// .with(DesignSpaceExplorationAdapter.builder()
193// .transformations(assignFeatureRule, deleteEmptyClassRule, createClassRule, moveFeatureRule)
194// .objectives(new AlwaysSatisfiedRandomHardObjective())
195//// .strategy(new DepthFirstStrategy().withDepthLimit(3).continueIfHardObjectivesFulfilled()
196// .strategy(new BestFirstStrategy().withDepthLimit(6).continueIfHardObjectivesFulfilled()
197//// .goOnOnlyIfFitnessIsBetter()
198// ))
199// .build();
200//
201// var model = store.createEmptyModel();
202// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
203//// dseAdapter.setRandom(1);
204// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
205//
206//// var modelInterpretation = model.getInterpretation(classModel);
207// var nameInterpretation = model.getInterpretation(name);
208// var methodInterpretation = model.getInterpretation(method);
209// var attributeInterpretation = model.getInterpretation(attribute);
210// var dataDependencyInterpretation = model.getInterpretation(dataDependency);
211// var functionalDependencyInterpretation = model.getInterpretation(functionalDependency);
212//
213//// var modelElement = dseAdapter.createObject();
214// var method1 = dseAdapter.createObject();
215// var method1Id = method1.get(0);
216// var method2 = dseAdapter.createObject();
217// var method2Id = method2.get(0);
218// var method3 = dseAdapter.createObject();
219// var method3Id = method3.get(0);
220// var method4 = dseAdapter.createObject();
221// var method4Id = method4.get(0);
222// var attribute1 = dseAdapter.createObject();
223// var attribute1Id = attribute1.get(0);
224// var attribute2 = dseAdapter.createObject();
225// var attribute2Id = attribute2.get(0);
226// var attribute3 = dseAdapter.createObject();
227// var attribute3Id = attribute3.get(0);
228// var attribute4 = dseAdapter.createObject();
229// var attribute4Id = attribute4.get(0);
230// var attribute5 = dseAdapter.createObject();
231// var attribute5Id = attribute5.get(0);
232//
233// nameInterpretation.put(method1, "M1");
234// nameInterpretation.put(method2, "M2");
235// nameInterpretation.put(method3, "M3");
236// nameInterpretation.put(method4, "M4");
237// nameInterpretation.put(attribute1, "A1");
238// nameInterpretation.put(attribute2, "A2");
239// nameInterpretation.put(attribute3, "A3");
240// nameInterpretation.put(attribute4, "A4");
241// nameInterpretation.put(attribute5, "A5");
242//
243//
244//
245//// modelInterpretation.put(modelElement, true);
246// methodInterpretation.put(method1, true);
247// methodInterpretation.put(method2, true);
248// methodInterpretation.put(method3, true);
249// methodInterpretation.put(method4, true);
250// attributeInterpretation.put(attribute1, true);
251// attributeInterpretation.put(attribute2, true);
252// attributeInterpretation.put(attribute3, true);
253// attributeInterpretation.put(attribute4, true);
254// attributeInterpretation.put(attribute5, true);
255//
256// dataDependencyInterpretation.put(Tuple.of(method1Id, attribute1Id), true);
257// dataDependencyInterpretation.put(Tuple.of(method1Id, attribute3Id), true);
258// dataDependencyInterpretation.put(Tuple.of(method2Id, attribute2Id), true);
259// dataDependencyInterpretation.put(Tuple.of(method3Id, attribute3Id), true);
260// dataDependencyInterpretation.put(Tuple.of(method3Id, attribute4Id), true);
261// dataDependencyInterpretation.put(Tuple.of(method4Id, attribute3Id), true);
262// dataDependencyInterpretation.put(Tuple.of(method4Id, attribute5Id), true);
263//
264// functionalDependencyInterpretation.put(Tuple.of(method1Id, attribute3Id), true);
265// functionalDependencyInterpretation.put(Tuple.of(method1Id, attribute4Id), true);
266// functionalDependencyInterpretation.put(Tuple.of(method2Id, attribute1Id), true);
267// functionalDependencyInterpretation.put(Tuple.of(method3Id, attribute1Id), true);
268// functionalDependencyInterpretation.put(Tuple.of(method3Id, attribute4Id), true);
269// functionalDependencyInterpretation.put(Tuple.of(method4Id, attribute2Id), true);
270//
271// queryEngine.flushChanges();
272//
273// var states = dseAdapter.explore();
274// System.out.println("states size: " + states.size());
275// }
276//*/
277}
diff --git a/subprojects/store-dse/src/test/java/tools/refinery/store/dse/DebugTest.java b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/DebugTest.java
new file mode 100644
index 00000000..1d757a5f
--- /dev/null
+++ b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/DebugTest.java
@@ -0,0 +1,116 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse;
7
8import tools.refinery.store.query.view.AnySymbolView;
9import tools.refinery.store.query.view.KeyOnlyView;
10import tools.refinery.store.representation.Symbol;
11
12class DebugTest {
13 private static final Symbol<Boolean> classModel = Symbol.of("ClassModel", 1);
14 private static final Symbol<Boolean> classElement = Symbol.of("ClassElement", 1);
15 private static final Symbol<Boolean> feature = Symbol.of("Feature", 1);
16
17 private static final Symbol<Boolean> isEncapsulatedBy = Symbol.of("IsEncapsulatedBy", 2);
18 private static final Symbol<Boolean> encapsulates = Symbol.of("Encapsulates", 2);
19
20 private static final Symbol<Boolean> features = Symbol.of("Features", 2);
21 private static final Symbol<Boolean> classes = Symbol.of("Classes", 2);
22
23 private static final AnySymbolView classModelView = new KeyOnlyView<>(classModel);
24 private static final AnySymbolView classElementView = new KeyOnlyView<>(classElement);
25 private static final AnySymbolView featureView = new KeyOnlyView<>(feature);
26 private static final AnySymbolView isEncapsulatedByView = new KeyOnlyView<>(isEncapsulatedBy);
27 private static final AnySymbolView encapsulatesView = new KeyOnlyView<>(encapsulates);
28 private static final AnySymbolView featuresView = new KeyOnlyView<>(features);
29 private static final AnySymbolView classesView = new KeyOnlyView<>(classes);
30
31
32// @Test
33// @Disabled("This test is only for debugging purposes")
34// void BFSTest() {
35// var createClassPrecondition = Query.of("CreateClassPrecondition",
36// (builder, model) -> builder.clause(
37// classModelView.call(model)
38// ));
39//
40// var createClassRule = new TransformationRule("CreateClass",
41// createClassPrecondition,
42// (model) -> {
43// var classesInterpretation = model.getInterpretation(classes);
44// var classElementInterpretation = model.getInterpretation(classElement);
45// return ((Tuple activation) -> {
46// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
47// var modelElement = activation.get(0);
48//
49// var newClassElement = dseAdapter.createObject();
50// var newClassElementId = newClassElement.get(0);
51//
52// classesInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
53// classElementInterpretation.put(Tuple.of(newClassElementId), true);
54// });
55// });
56//
57// var createFeaturePrecondition = Query.of("CreateFeaturePrecondition",
58// (builder, model) -> builder.clause(
59// classModelView.call(model)
60// ));
61//
62// var createFeatureRule = new TransformationRule("CreateFeature",
63// createFeaturePrecondition,
64// (model) -> {
65// var featuresInterpretation = model.getInterpretation(features);
66// var featureInterpretation = model.getInterpretation(feature);
67// return ((Tuple activation) -> {
68// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
69// var modelElement = activation.get(0);
70//
71// var newClassElement = dseAdapter.createObject();
72// var newClassElementId = newClassElement.get(0);
73//
74// featuresInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
75// featureInterpretation.put(Tuple.of(newClassElementId), true);
76// });
77// });
78//
79// var store = ModelStore.builder()
80// .symbols(classModel, classElement, feature, isEncapsulatedBy, encapsulates, classes, features)
81// .with(ViatraModelQueryAdapter.builder()
82// .queries(createClassPrecondition, createFeaturePrecondition))
83// .with(ModelVisualizerAdapter.builder()
84// .withOutputpath("test_output")
85// .withFormat(FileFormat.DOT)
86// .withFormat(FileFormat.SVG)
87// .saveStates()
88// .saveDesignSpace()
89// )
90// .with(DesignSpaceExplorationAdapter.builder()
91// .transformations(createClassRule, createFeatureRule)
92// .objectives(new AlwaysSatisfiedRandomHardObjective())
93// .strategy(new DepthFirstStrategy().withDepthLimit(4).continueIfHardObjectivesFulfilled()
94//// .strategy(new BestFirstStrategy().withDepthLimit(4).continueIfHardObjectivesFulfilled()
95//// .goOnOnlyIfFitnessIsBetter()
96// ))
97// .build();
98//
99// var model = store.createEmptyModel();
100// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
101//// dseAdapter.setRandom(1);
102// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
103//
104// var modelElementInterpretation = model.getInterpretation(classModel);
105// var classElementInterpretation = model.getInterpretation(classElement);
106// var modelElement = dseAdapter.createObject();
107// modelElementInterpretation.put(modelElement, true);
108// classElementInterpretation.put(modelElement, true);
109// queryEngine.flushChanges();
110//
111//
112// var states = dseAdapter.explore();
113// System.out.println("states size: " + states.size());
114//
115// }
116}
diff --git a/subprojects/store-dse/src/test/java/tools/refinery/store/dse/DesignSpaceExplorationTest.java b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/DesignSpaceExplorationTest.java
new file mode 100644
index 00000000..f5f13433
--- /dev/null
+++ b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/DesignSpaceExplorationTest.java
@@ -0,0 +1,596 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse;
7
8import tools.refinery.store.query.view.AnySymbolView;
9import tools.refinery.store.query.view.KeyOnlyView;
10import tools.refinery.store.representation.Symbol;
11
12class DesignSpaceExplorationTest {
13// private static final Symbol<Boolean> namedElement = Symbol.of("NamedElement", 1);
14// private static final Symbol<Boolean> attribute = Symbol.of("Attribute", 1);
15// private static final Symbol<Boolean> method = Symbol.of("Method", 1);
16// private static final Symbol<Boolean> dataDependency = Symbol.of("DataDependency", 2);
17// private static final Symbol<Boolean> functionalDependency = Symbol.of("FunctionalDependency", 2);
18
19 private static final Symbol<Boolean> classModel = Symbol.of("ClassModel", 1);
20 private static final Symbol<Boolean> classElement = Symbol.of("ClassElement", 1);
21 private static final Symbol<Boolean> feature = Symbol.of("Feature", 1);
22
23 private static final Symbol<Boolean> isEncapsulatedBy = Symbol.of("IsEncapsulatedBy", 2);
24 private static final Symbol<Boolean> encapsulates = Symbol.of("Encapsulates", 2);
25
26 private static final Symbol<Boolean> features = Symbol.of("Features", 2);
27 private static final Symbol<Boolean> classes = Symbol.of("Classes", 2);
28
29 private static final AnySymbolView classModelView = new KeyOnlyView<>(classModel);
30 private static final AnySymbolView classElementView = new KeyOnlyView<>(classElement);
31 private static final AnySymbolView featureView = new KeyOnlyView<>(feature);
32 private static final AnySymbolView isEncapsulatedByView = new KeyOnlyView<>(isEncapsulatedBy);
33 private static final AnySymbolView encapsulatesView = new KeyOnlyView<>(encapsulates);
34 private static final AnySymbolView featuresView = new KeyOnlyView<>(features);
35 private static final AnySymbolView classesView = new KeyOnlyView<>(classes);
36
37// @Test
38// void createObjectTest() {
39// var store = ModelStore.builder()
40// .with(ViatraModelQueryAdapter.builder())
41// .with(DesignSpaceExplorationAdapter.builder()
42// .strategy(new DepthFirstStrategy().withDepthLimit(0)))
43// .build();
44//
45// var model = store.createEmptyModel();
46// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
47//
48// assertEquals(0, dseAdapter.getModelSize());
49//
50// var newModel = dseAdapter.createObject();
51// var newModelId = newModel.get(0);
52// var newClass1 = dseAdapter.createObject();
53// var newClass1Id = newClass1.get(0);
54// var newClass2 = dseAdapter.createObject();
55// var newClass2Id = newClass2.get(0);
56// var newField = dseAdapter.createObject();
57// var newFieldId = newField.get(0);
58//
59// assertEquals(0, newModelId);
60// assertEquals(1, newClass1Id);
61// assertEquals(2, newClass2Id);
62// assertEquals(3, newFieldId);
63// assertEquals(4, dseAdapter.getModelSize());
64// }
65
66// @Test
67// void deleteMiddleObjectTest() {
68// var store = ModelStore.builder()
69// .with(ViatraModelQueryAdapter.builder())
70// .with(DesignSpaceExplorationAdapter.builder()
71// .strategy(new DepthFirstStrategy()))
72// .build();
73//
74// var model = store.createEmptyModel();
75// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
76//
77// assertEquals(0, dseAdapter.getModelSize());
78//
79// var newObject0 = dseAdapter.createObject();
80// var newObject0Id = newObject0.get(0);
81// var newObject1 = dseAdapter.createObject();
82// var newObject1Id = newObject1.get(0);
83// var newObject2 = dseAdapter.createObject();
84// var newObject2Id = newObject2.get(0);
85// var newObject3 = dseAdapter.createObject();
86// var newObject3Id = newObject3.get(0);
87//
88// assertEquals(0, newObject0Id);
89// assertEquals(1, newObject1Id);
90// assertEquals(2, newObject2Id);
91// assertEquals(3, newObject3Id);
92// assertEquals(4, dseAdapter.getModelSize());
93//
94// dseAdapter.deleteObject(newObject1);
95// assertEquals(4, dseAdapter.getModelSize());
96//
97// var newObject4 = dseAdapter.createObject();
98// var newObject4Id = newObject4.get(0);
99// assertEquals(4, newObject4Id);
100// assertEquals(5, dseAdapter.getModelSize());
101//
102// dseAdapter.deleteObject(newObject4);
103// assertEquals(5, dseAdapter.getModelSize());
104// }
105//
106// @Test
107// void DFSTrivialTest() {
108// var store = ModelStore.builder()
109// .symbols(classModel)
110// .with(ViatraModelQueryAdapter.builder())
111// .with(DesignSpaceExplorationAdapter.builder()
112// .strategy(new DepthFirstStrategy().withDepthLimit(0)))
113// .build();
114//
115// var model = store.createEmptyModel();
116// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
117//
118// var states = dseAdapter.explore();
119// assertEquals(1, states.size());
120// }
121//
122// @Test
123// void DFSOneRuleTest() {
124// var createClassPrecondition = Query.of("CreateClassPrecondition",
125// (builder, model) -> builder.clause(
126// classModelView.call(model)
127// ));
128//
129// var createClassRule = new TransformationRule("CreateClass",
130// createClassPrecondition,
131// (model) -> {
132// var classesInterpretation = model.getInterpretation(classes);
133// var classElementInterpretation = model.getInterpretation(classElement);
134// return ((Tuple activation) -> {
135// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
136// var modelElement = activation.get(0);
137//
138// var newClassElement = dseAdapter.createObject();
139// var newClassElementId = newClassElement.get(0);
140//
141// classesInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
142// classElementInterpretation.put(Tuple.of(newClassElementId), true);
143// });
144// });
145//
146// var store = ModelStore.builder()
147// .symbols(classModel, classElement, classes)
148// .with(ViatraModelQueryAdapter.builder()
149// .queries(createClassPrecondition))
150// .with(DesignSpaceExplorationAdapter.builder()
151// .transformations(createClassRule)
152// .strategy(new DepthFirstStrategy().withDepthLimit(0)
153// ))
154// .build();
155//
156// var model = store.createEmptyModel();
157// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
158// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
159//
160// var modelElementInterpretation = model.getInterpretation(classModel);
161// modelElementInterpretation.put(dseAdapter.createObject(), true);
162// queryEngine.flushChanges();
163//
164// var states = dseAdapter.explore();
165// assertEquals(1, states.size());
166// }
167//
168// @Test
169// void DFSContinueTest() {
170// var createClassPrecondition = Query.of("CreateClassPrecondition",
171// (builder, model) -> builder.clause(
172// classModelView.call(model)
173// ));
174//
175// var createClassRule = new TransformationRule("CreateClass",
176// createClassPrecondition,
177// (model) -> {
178// var classesInterpretation = model.getInterpretation(classes);
179// var classElementInterpretation = model.getInterpretation(classElement);
180// return ((Tuple activation) -> {
181// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
182// var modelElement = activation.get(0);
183//
184// var newClassElement = dseAdapter.createObject();
185// var newClassElementId = newClassElement.get(0);
186//
187// classesInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
188// classElementInterpretation.put(Tuple.of(newClassElementId), true);
189// });
190// });
191//
192// var store = ModelStore.builder()
193// .symbols(classModel, classElement, classes)
194// .with(ViatraModelQueryAdapter.builder()
195// .queries(createClassPrecondition))
196// .with(DesignSpaceExplorationAdapter.builder()
197// .transformations(createClassRule)
198// .strategy(new DepthFirstStrategy().withDepthLimit(4).continueIfHardObjectivesFulfilled()
199// ))
200// .build();
201//
202// var model = store.createEmptyModel();
203// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
204// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
205//
206// var modelElementInterpretation = model.getInterpretation(classModel);
207// modelElementInterpretation.put(dseAdapter.createObject(), true);
208// queryEngine.flushChanges();
209//
210// var states = dseAdapter.explore();
211// assertEquals(5, states.size());
212// }
213//
214// @Test
215// void DFSCompletenessTest() {
216// var createClassPrecondition = Query.of("CreateClassPrecondition",
217// (builder, model) -> builder.clause(
218// classModelView.call(model)
219// ));
220//
221// var createClassRule = new TransformationRule("CreateClass",
222// createClassPrecondition,
223// (model) -> {
224// var classesInterpretation = model.getInterpretation(classes);
225// var classElementInterpretation = model.getInterpretation(classElement);
226// return ((Tuple activation) -> {
227// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
228// var modelElement = activation.get(0);
229//
230// var newClassElement = dseAdapter.createObject();
231// var newClassElementId = newClassElement.get(0);
232//
233// classesInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
234// classElementInterpretation.put(Tuple.of(newClassElementId), true);
235// });
236// });
237//
238// var createFeaturePrecondition = Query.of("CreateFeaturePrecondition",
239// (builder, model) -> builder.clause(
240// classModelView.call(model)
241// ));
242//
243// var createFeatureRule = new TransformationRule("CreateFeature",
244// createFeaturePrecondition,
245// (model) -> {
246// var featuresInterpretation = model.getInterpretation(features);
247// var featureInterpretation = model.getInterpretation(feature);
248// return ((Tuple activation) -> {
249// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
250// var modelElement = activation.get(0);
251//
252// var newClassElement = dseAdapter.createObject();
253// var newClassElementId = newClassElement.get(0);
254//
255// featuresInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
256// featureInterpretation.put(Tuple.of(newClassElementId), true);
257// });
258// });
259//
260// var store = ModelStore.builder()
261// .symbols(classModel, classElement, classes, feature, features, isEncapsulatedBy, encapsulates)
262// .with(ViatraModelQueryAdapter.builder()
263// .queries(createClassPrecondition, createFeaturePrecondition))
264// .with(DesignSpaceExplorationAdapter.builder()
265// .transformations(createClassRule, createFeatureRule)
266// .strategy(new DepthFirstStrategy().withDepthLimit(10).continueIfHardObjectivesFulfilled()
267// ))
268// .build();
269//
270// var model = store.createEmptyModel();
271// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
272// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
273//
274// var modelElementInterpretation = model.getInterpretation(classModel);
275// modelElementInterpretation.put(dseAdapter.createObject(), true);
276// queryEngine.flushChanges();
277//
278// var states = dseAdapter.explore();
279// assertEquals(2047, states.size());
280// }
281//
282// @Test
283// void DFSSolutionLimitTest() {
284// var createClassPrecondition = Query.of("CreateClassPrecondition",
285// (builder, model) -> builder.clause(
286// classModelView.call(model)
287// ));
288//
289// var createClassRule = new TransformationRule("CreateClass",
290// createClassPrecondition,
291// (model) -> {
292// var classesInterpretation = model.getInterpretation(classes);
293// var classElementInterpretation = model.getInterpretation(classElement);
294// return ((Tuple activation) -> {
295// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
296// var modelElement = activation.get(0);
297//
298// var newClassElement = dseAdapter.createObject();
299// var newClassElementId = newClassElement.get(0);
300//
301// classesInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
302// classElementInterpretation.put(Tuple.of(newClassElementId), true);
303// });
304// });
305//
306// var createFeaturePrecondition = Query.of("CreateFeaturePrecondition",
307// (builder, model) -> builder.clause(
308// classModelView.call(model)
309// ));
310//
311// var createFeatureRule = new TransformationRule("CreateFeature",
312// createFeaturePrecondition,
313// (model) -> {
314// var featuresInterpretation = model.getInterpretation(features);
315// var featureInterpretation = model.getInterpretation(feature);
316// return ((Tuple activation) -> {
317// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
318// var modelElement = activation.get(0);
319//
320// var newClassElement = dseAdapter.createObject();
321// var newClassElementId = newClassElement.get(0);
322//
323// featuresInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
324// featureInterpretation.put(Tuple.of(newClassElementId), true);
325// });
326// });
327//
328// var store = ModelStore.builder()
329// .symbols(classModel, classElement, classes, feature, features, isEncapsulatedBy, encapsulates)
330// .with(ViatraModelQueryAdapter.builder()
331// .queries(createClassPrecondition, createFeaturePrecondition))
332// .with(DesignSpaceExplorationAdapter.builder()
333// .transformations(createClassRule, createFeatureRule)
334// .strategy(new DepthFirstStrategy().withSolutionLimit(222)
335// .continueIfHardObjectivesFulfilled()
336// ))
337// .build();
338//
339// var model = store.createEmptyModel();
340// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
341// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
342//
343// var modelElementInterpretation = model.getInterpretation(classModel);
344// modelElementInterpretation.put(dseAdapter.createObject(), true);
345// queryEngine.flushChanges();
346//
347// var states = dseAdapter.explore();
348// assertEquals(222, states.size());
349// }
350//
351// @Test
352// void BeFSTrivialTest() {
353// var store = ModelStore.builder()
354// .symbols(classModel)
355// .with(ViatraModelQueryAdapter.builder())
356// .with(DesignSpaceExplorationAdapter.builder()
357// .strategy(new BestFirstStrategy().withDepthLimit(0)))
358// .build();
359//
360// var model = store.createEmptyModel();
361// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
362//
363// var states = dseAdapter.explore();
364// assertEquals(1, states.size());
365// }
366//
367// @Test
368// void BeFSOneRuleTest() {
369// var createClassPrecondition = Query.of("CreateClassPrecondition",
370// (builder, model) -> builder.clause(
371// classModelView.call(model)
372// ));
373//
374// var createClassRule = new TransformationRule("CreateClass",
375// createClassPrecondition,
376// (model) -> {
377// var classesInterpretation = model.getInterpretation(classes);
378// var classElementInterpretation = model.getInterpretation(classElement);
379// return ((Tuple activation) -> {
380// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
381// var modelElement = activation.get(0);
382//
383// var newClassElement = dseAdapter.createObject();
384// var newClassElementId = newClassElement.get(0);
385//
386// classesInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
387// classElementInterpretation.put(Tuple.of(newClassElementId), true);
388// });
389// });
390//
391// var store = ModelStore.builder()
392// .symbols(classModel, classElement, classes)
393// .with(ViatraModelQueryAdapter.builder()
394// .queries(createClassPrecondition))
395// .with(DesignSpaceExplorationAdapter.builder()
396// .transformations(createClassRule)
397// .strategy(new BestFirstStrategy().withDepthLimit(4)
398// ))
399// .build();
400//
401// var model = store.createEmptyModel();
402// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
403// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
404//
405// var modelElementInterpretation = model.getInterpretation(classModel);
406// modelElementInterpretation.put(dseAdapter.createObject(), true);
407// queryEngine.flushChanges();
408//
409// var states = dseAdapter.explore();
410// assertEquals(1, states.size());
411// }
412//
413// @Test
414// void BeFSContinueTest() {
415// var createClassPrecondition = Query.of("CreateClassPrecondition",
416// (builder, model) -> builder.clause(
417// classModelView.call(model)
418// ));
419//
420// var createClassRule = new TransformationRule("CreateClass",
421// createClassPrecondition,
422// (model) -> {
423// var classesInterpretation = model.getInterpretation(classes);
424// var classElementInterpretation = model.getInterpretation(classElement);
425// return ((Tuple activation) -> {
426// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
427// var modelElement = activation.get(0);
428//
429// var newClassElement = dseAdapter.createObject();
430// var newClassElementId = newClassElement.get(0);
431//
432// classesInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
433// classElementInterpretation.put(Tuple.of(newClassElementId), true);
434// });
435// });
436//
437// var store = ModelStore.builder()
438// .symbols(classModel, classElement, classes)
439// .with(ViatraModelQueryAdapter.builder()
440// .queries(createClassPrecondition))
441// .with(DesignSpaceExplorationAdapter.builder()
442// .transformations(createClassRule)
443// .strategy(new BestFirstStrategy().withDepthLimit(4).continueIfHardObjectivesFulfilled()
444// ))
445// .build();
446//
447// var model = store.createEmptyModel();
448// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
449// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
450//
451// var modelElementInterpretation = model.getInterpretation(classModel);
452// modelElementInterpretation.put(dseAdapter.createObject(), true);
453// queryEngine.flushChanges();
454//
455// var states = dseAdapter.explore();
456// assertEquals(5, states.size());
457// }
458//
459// @Test
460// void BeFSCompletenessTest() {
461// var createClassPrecondition = Query.of("CreateClassPrecondition",
462// (builder, model) -> builder.clause(
463// classModelView.call(model)
464// ));
465//
466// var createClassRule = new TransformationRule("CreateClass",
467// createClassPrecondition,
468// (model) -> {
469// var classesInterpretation = model.getInterpretation(classes);
470// var classElementInterpretation = model.getInterpretation(classElement);
471// return ((Tuple activation) -> {
472// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
473// var modelElement = activation.get(0);
474//
475// var newClassElement = dseAdapter.createObject();
476// var newClassElementId = newClassElement.get(0);
477//
478// classesInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
479// classElementInterpretation.put(Tuple.of(newClassElementId), true);
480// });
481// });
482//
483// var createFeaturePrecondition = Query.of("CreateFeaturePrecondition",
484// (builder, model) -> builder.clause(
485// classModelView.call(model)
486// ));
487//
488// var createFeatureRule = new TransformationRule("CreateFeature",
489// createFeaturePrecondition,
490// (model) -> {
491// var featuresInterpretation = model.getInterpretation(features);
492// var featureInterpretation = model.getInterpretation(feature);
493// return ((Tuple activation) -> {
494// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
495// var modelElement = activation.get(0);
496//
497// var newClassElement = dseAdapter.createObject();
498// var newClassElementId = newClassElement.get(0);
499//
500// featuresInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
501// featureInterpretation.put(Tuple.of(newClassElementId), true);
502// });
503// });
504//
505// var store = ModelStore.builder()
506// .symbols(classModel, classElement, classes, feature, features, isEncapsulatedBy, encapsulates)
507// .with(ViatraModelQueryAdapter.builder()
508// .queries(createClassPrecondition, createFeaturePrecondition))
509// .with(DesignSpaceExplorationAdapter.builder()
510// .transformations(createClassRule, createFeatureRule)
511// .strategy(new BestFirstStrategy().withDepthLimit(10).continueIfHardObjectivesFulfilled()
512// ))
513// .build();
514//
515// var model = store.createEmptyModel();
516// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
517// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
518//
519// var modelElementInterpretation = model.getInterpretation(classModel);
520// modelElementInterpretation.put(dseAdapter.createObject(), true);
521// queryEngine.flushChanges();
522//
523// var states = dseAdapter.explore();
524// assertEquals(2047, states.size());
525// }
526//
527// @Test
528// void BeFSSolutionLimitTest() {
529// var createClassPrecondition = Query.of("CreateClassPrecondition",
530// (builder, model) -> builder.clause(
531// classModelView.call(model)
532// ));
533//
534// var createClassRule = new TransformationRule("CreateClass",
535// createClassPrecondition,
536// (model) -> {
537// var classesInterpretation = model.getInterpretation(classes);
538// var classElementInterpretation = model.getInterpretation(classElement);
539// return ((Tuple activation) -> {
540// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
541// var modelElement = activation.get(0);
542//
543// var newClassElement = dseAdapter.createObject();
544// var newClassElementId = newClassElement.get(0);
545//
546// classesInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
547// classElementInterpretation.put(Tuple.of(newClassElementId), true);
548// });
549// });
550//
551// var createFeaturePrecondition = Query.of("CreateFeaturePrecondition",
552// (builder, model) -> builder.clause(
553// classModelView.call(model)
554// ));
555//
556// var createFeatureRule = new TransformationRule("CreateFeature",
557// createFeaturePrecondition,
558// (model) -> {
559// var featuresInterpretation = model.getInterpretation(features);
560// var featureInterpretation = model.getInterpretation(feature);
561// return ((Tuple activation) -> {
562// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
563// var modelElement = activation.get(0);
564//
565// var newClassElement = dseAdapter.createObject();
566// var newClassElementId = newClassElement.get(0);
567//
568// featuresInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
569// featureInterpretation.put(Tuple.of(newClassElementId), true);
570// });
571// });
572//
573// var store = ModelStore.builder()
574// .symbols(classModel, classElement, classes, feature, features, isEncapsulatedBy, encapsulates)
575// .with(ViatraModelQueryAdapter.builder()
576// .queries(createClassPrecondition, createFeaturePrecondition))
577// .with(DesignSpaceExplorationAdapter.builder()
578// .transformations(createClassRule, createFeatureRule)
579// .strategy(new BestFirstStrategy().withSolutionLimit(222)
580// .continueIfHardObjectivesFulfilled()
581// ))
582// .build();
583//
584// var model = store.createEmptyModel();
585// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
586// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
587//
588// var modelElementInterpretation = model.getInterpretation(classModel);
589// modelElementInterpretation.put(dseAdapter.createObject(), true);
590// queryEngine.flushChanges();
591//
592// var states = dseAdapter.explore();
593// assertEquals(222, states.size());
594// }
595
596}
diff --git a/subprojects/store-dse/src/test/java/tools/refinery/store/dse/TransformationRuleTest.java b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/TransformationRuleTest.java
new file mode 100644
index 00000000..43b04e0d
--- /dev/null
+++ b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/TransformationRuleTest.java
@@ -0,0 +1,414 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse;
7
8import org.junit.jupiter.api.Test;
9//
10//import tools.refinery.store.dse.transition.DesignSpaceExplorationAdapter;
11//import tools.refinery.store.model.ModelStore;
12//import tools.refinery.store.query.ModelQueryAdapter;
13//import tools.refinery.store.query.dnf.Query;
14//import tools.refinery.store.dse.transition.TransformationRule;
15//import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
16//import tools.refinery.store.query.view.AnySymbolView;
17//import tools.refinery.store.query.view.KeyOnlyView;
18//import tools.refinery.store.representation.Symbol;
19//import tools.refinery.store.tuple.Tuple;
20//
21//import java.util.List;
22//import java.util.Map;
23//
24//import static org.junit.jupiter.api.Assertions.assertEquals;
25//import static tools.refinery.store.query.literal.Literals.not;
26//import static tools.refinery.store.dse.tests.QueryAssertions.assertResults;
27//
28class TransformationRuleTest {
29//
30// private static final Symbol<Boolean> classModel = Symbol.of("ClassModel", 1);
31// private static final Symbol<Boolean> classElement = Symbol.of("ClassElement", 1);
32// private static final Symbol<Boolean> feature = Symbol.of("Feature", 1);
33//
34// private static final Symbol<Boolean> isEncapsulatedBy = Symbol.of("IsEncapsulatedBy", 2);
35// private static final Symbol<Boolean> encapsulates = Symbol.of("Encapsulates", 2);
36//
37// private static final Symbol<Boolean> features = Symbol.of("Features", 2);
38// private static final Symbol<Boolean> classes = Symbol.of("Classes", 2);
39//
40// private static final AnySymbolView classModelView = new KeyOnlyView<>(classModel);
41// private static final AnySymbolView classElementView = new KeyOnlyView<>(classElement);
42// private static final AnySymbolView featureView = new KeyOnlyView<>(feature);
43// private static final AnySymbolView isEncapsulatedByView = new KeyOnlyView<>(isEncapsulatedBy);
44// private static final AnySymbolView encapsulatesView = new KeyOnlyView<>(encapsulates);
45// private static final AnySymbolView featuresView = new KeyOnlyView<>(features);
46// private static final AnySymbolView classesView = new KeyOnlyView<>(classes);
47//
48// @Test
49// void activationsTest() {
50// var assignFeaturePreconditionHelper = Query.of("AssignFeaturePreconditionHelper",
51// (builder, model, c, f) -> builder.clause(
52// classElementView.call(c),
53// classesView.call(model, c),
54// encapsulatesView.call(c, f)
55// ));
56//
57// var assignFeaturePrecondition = Query.of("AssignFeaturePrecondition", (builder, c2, f)
58// -> builder.clause((model, c1) -> List.of(
59// classModelView.call(model),
60// featureView.call(f),
61// classElementView.call(c2),
62// featuresView.call(model, f),
63// classesView.call(model, c1),
64// not(assignFeaturePreconditionHelper.call(model, c2, f)),
65// not(encapsulatesView.call(c2, f))
66// )));
67//
68// var deleteEmptyClassPrecondition = Query.of("DeleteEmptyClassPrecondition",
69// (builder, model, c) -> builder.clause((f) -> List.of(
70// classModelView.call(model),
71// classElementView.call(c),
72// featuresView.call(model, f),
73// not(encapsulatesView.call(c, f))
74// )));
75//
76// TransformationRule assignFeatureRule = new TransformationRule("AssignFeature",
77// assignFeaturePrecondition,
78// (model) -> {
79// var isEncapsulatedByInterpretation = model.getInterpretation(isEncapsulatedBy);
80// return ((Tuple activation) -> {
81// var feature = activation.get(0);
82// var classElement = activation.get(1);
83//
84// isEncapsulatedByInterpretation.put(Tuple.of(feature, classElement), true);
85// });
86// });
87//
88// TransformationRule deleteEmptyClassRule = new TransformationRule("DeleteEmptyClass",
89// deleteEmptyClassPrecondition,
90// (model) -> {
91// var classesInterpretation = model.getInterpretation(classes);
92// var classElementInterpretation = model.getInterpretation(classElement);
93// return ((Tuple activation) -> {
94// var modelElement = activation.get(0);
95// var classElement = activation.get(1);
96//
97// classesInterpretation.put(Tuple.of(modelElement, classElement), false);
98// classElementInterpretation.put(Tuple.of(classElement), false);
99// });
100// });
101//
102//
103// var store = ModelStore.builder()
104// .symbols(classModel, classElement, feature, isEncapsulatedBy, encapsulates, classes, features)
105// .with(ViatraModelQueryAdapter.builder()
106// .queries(assignFeaturePrecondition, assignFeaturePreconditionHelper,
107// deleteEmptyClassPrecondition))
108// .with(DesignSpaceExplorationAdapter.builder()
109// .strategy(new DepthFirstStrategy().withDepthLimit(0)))
110// .build();
111//
112// var model = store.createEmptyModel();
113// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
114// assignFeatureRule.prepare(model, queryEngine);
115// deleteEmptyClassRule.prepare(model, queryEngine);
116//
117// var classModelInterpretation = model.getInterpretation(classModel);
118// var classElementInterpretation = model.getInterpretation(classElement);
119// var featureInterpretation = model.getInterpretation(feature);
120// var featuresInterpretation = model.getInterpretation(features);
121// var classesInterpretation = model.getInterpretation(classes);
122//
123// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
124// var newModel = dseAdapter.createObject();
125// var newModelId = newModel.get(0);
126// var newClass1 = dseAdapter.createObject();
127// var newClass1Id = newClass1.get(0);
128// var newClass2 = dseAdapter.createObject();
129// var newClass2Id = newClass2.get(0);
130// var newField = dseAdapter.createObject();
131// var newFieldId = newField.get(0);
132//
133// classModelInterpretation.put(newModel, true);
134// classElementInterpretation.put(newClass1, true);
135// classElementInterpretation.put(newClass2, true);
136// featureInterpretation.put(newField, true);
137// classesInterpretation.put(Tuple.of(newModelId, newClass1Id), true);
138// classesInterpretation.put(Tuple.of(newModelId, newClass2Id), true);
139// featuresInterpretation.put(Tuple.of(newModelId, newFieldId), true);
140//
141// queryEngine.flushChanges();
142//
143// var assignFeatureRuleActivations = assignFeatureRule.getAllActivationsAsResultSet();
144// var deleteEmptyClassRuleActivations = deleteEmptyClassRule.getAllActivationsAsResultSet();
145//
146// assertResults(Map.of(
147// Tuple.of(newClass1Id, newFieldId), true,
148// Tuple.of(newClass2Id, newFieldId), true
149// ), assignFeatureRuleActivations);
150//
151// assertResults(Map.of(
152// Tuple.of(newModelId, newClass1Id), true,
153// Tuple.of(newModelId, newClass2Id), true
154// ), deleteEmptyClassRuleActivations);
155// }
156//
157// @Test
158// void randomActivationTest() {
159// var deleteEmptyClassPrecondition = Query.of("DeleteEmptyClassPrecondition",
160// (builder, model, c) -> builder.clause((f) -> List.of(
161// classModelView.call(model),
162// classElementView.call(c),
163// featuresView.call(model, f),
164// not(encapsulatesView.call(c, f))
165// )));
166//
167// TransformationRule deleteEmptyClassRule0 = new TransformationRule("DeleteEmptyClass0",
168// deleteEmptyClassPrecondition,
169// (model) -> {
170// var classesInterpretation = model.getInterpretation(classes);
171// var classElementInterpretation = model.getInterpretation(classElement);
172// return ((Tuple activation) -> {
173// var modelElement = activation.get(0);
174// var classElement = activation.get(1);
175//
176// classesInterpretation.put(Tuple.of(modelElement, classElement), false);
177// classElementInterpretation.put(Tuple.of(classElement), false);
178// });
179// },
180// 0L);
181//
182// TransformationRule deleteEmptyClassRule1 = new TransformationRule("DeleteEmptyClass1",
183// deleteEmptyClassPrecondition,
184// (model) -> {
185// var classesInterpretation = model.getInterpretation(classes);
186// var classElementInterpretation = model.getInterpretation(classElement);
187// return ((Tuple activation) -> {
188// var modelElement = activation.get(0);
189// var classElement = activation.get(1);
190//
191// classesInterpretation.put(Tuple.of(modelElement, classElement), false);
192// classElementInterpretation.put(Tuple.of(classElement), false);
193// });
194// },
195// 78634L);
196//
197// var store = ModelStore.builder()
198// .symbols(classModel, classElement, feature, isEncapsulatedBy, encapsulates, classes, features)
199// .with(ViatraModelQueryAdapter.builder()
200// .queries(deleteEmptyClassPrecondition))
201// .with(DesignSpaceExplorationAdapter.builder()
202// .strategy(new DepthFirstStrategy().withDepthLimit(0)))
203// .build();
204//
205// var model = store.createEmptyModel();
206// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
207// deleteEmptyClassRule0.prepare(model, queryEngine);
208// deleteEmptyClassRule1.prepare(model, queryEngine);
209//
210// var classModelInterpretation = model.getInterpretation(classModel);
211// var classElementInterpretation = model.getInterpretation(classElement);
212// var featureInterpretation = model.getInterpretation(feature);
213// var featuresInterpretation = model.getInterpretation(features);
214// var classesInterpretation = model.getInterpretation(classes);
215//
216// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
217// var newModel = dseAdapter.createObject();
218// var newModelId = newModel.get(0);
219// var newClass1 = dseAdapter.createObject();
220// var newClass1Id = newClass1.get(0);
221// var newClass2 = dseAdapter.createObject();
222// var newClass2Id = newClass2.get(0);
223// var newField = dseAdapter.createObject();
224// var newFieldId = newField.get(0);
225//
226// classModelInterpretation.put(newModel, true);
227// classElementInterpretation.put(newClass1, true);
228// classElementInterpretation.put(newClass2, true);
229// featureInterpretation.put(newField, true);
230// classesInterpretation.put(Tuple.of(newModelId, newClass1Id), true);
231// classesInterpretation.put(Tuple.of(newModelId, newClass2Id), true);
232// featuresInterpretation.put(Tuple.of(newModelId, newFieldId), true);
233//
234// queryEngine.flushChanges();
235//
236//
237// var activation0 = deleteEmptyClassRule0.getRandomActivation().activation();
238// var activation1 = deleteEmptyClassRule1.getRandomActivation().activation();
239//
240// assertResults(Map.of(
241// Tuple.of(newModelId, newClass1Id), true,
242// Tuple.of(newModelId, newClass2Id), true
243// ), deleteEmptyClassRule0.getAllActivationsAsResultSet());
244//
245// assertResults(Map.of(
246// Tuple.of(newModelId, newClass1Id), true,
247// Tuple.of(newModelId, newClass2Id), true
248// ), deleteEmptyClassRule1.getAllActivationsAsResultSet());
249//
250// assertEquals(Tuple.of(newModelId, newClass2Id), activation0);
251// assertEquals(Tuple.of(newModelId, newClass1Id), activation1);
252//
253// }
254//
255// @Test
256// void fireTest() {
257// var deleteEmptyClassPrecondition = Query.of("DeleteEmptyClassPrecondition",
258// (builder, model, c) -> builder.clause((f) -> List.of(
259// classModelView.call(model),
260// classElementView.call(c),
261// featuresView.call(model, f),
262// not(encapsulatesView.call(c, f))
263// )));
264//
265// TransformationRule deleteEmptyClassRule = new TransformationRule("DeleteEmptyClass",
266// deleteEmptyClassPrecondition,
267// (model) -> {
268// var classesInterpretation = model.getInterpretation(classes);
269// var classElementInterpretation = model.getInterpretation(classElement);
270// return ((Tuple activation) -> {
271// var modelElement = activation.get(0);
272// var classElement = activation.get(1);
273//
274// classesInterpretation.put(Tuple.of(modelElement, classElement), false);
275// classElementInterpretation.put(Tuple.of(classElement), false);
276// });
277// });
278//
279// var store = ModelStore.builder()
280// .symbols(classModel, classElement, feature, isEncapsulatedBy, encapsulates, classes, features)
281// .with(ViatraModelQueryAdapter.builder()
282// .queries(deleteEmptyClassPrecondition))
283// .with(DesignSpaceExplorationAdapter.builder()
284// .strategy(new DepthFirstStrategy().withDepthLimit(0)))
285// .build();
286//
287// var model = store.createEmptyModel();
288// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
289// deleteEmptyClassRule.prepare(model, queryEngine);
290//
291// var classModelInterpretation = model.getInterpretation(classModel);
292// var classElementInterpretation = model.getInterpretation(classElement);
293// var featureInterpretation = model.getInterpretation(feature);
294// var featuresInterpretation = model.getInterpretation(features);
295// var classesInterpretation = model.getInterpretation(classes);
296//
297// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
298// var newModel = dseAdapter.createObject();
299// var newModelId = newModel.get(0);
300// var newClass1 = dseAdapter.createObject();
301// var newClass1Id = newClass1.get(0);
302// var newClass2 = dseAdapter.createObject();
303// var newClass2Id = newClass2.get(0);
304// var newField = dseAdapter.createObject();
305// var newFieldId = newField.get(0);
306//
307// classModelInterpretation.put(newModel, true);
308// classElementInterpretation.put(newClass1, true);
309// classElementInterpretation.put(newClass2, true);
310// featureInterpretation.put(newField, true);
311// classesInterpretation.put(Tuple.of(newModelId, newClass1Id), true);
312// classesInterpretation.put(Tuple.of(newModelId, newClass2Id), true);
313// featuresInterpretation.put(Tuple.of(newModelId, newFieldId), true);
314//
315// queryEngine.flushChanges();
316//
317// assertResults(Map.of(
318// Tuple.of(newModelId, newClass1Id), true,
319// Tuple.of(newModelId, newClass2Id), true
320// ), deleteEmptyClassRule.getAllActivationsAsResultSet());
321//
322//
323// deleteEmptyClassRule.fireActivation(Tuple.of(0, 1));
324//
325// assertResults(Map.of(
326// Tuple.of(newModelId, newClass1Id), false,
327// Tuple.of(newModelId, newClass2Id), true
328// ), deleteEmptyClassRule.getAllActivationsAsResultSet());
329// }
330//
331// @Test
332// void randomFireTest() {
333// var deleteEmptyClassPrecondition = Query.of("DeleteEmptyClassPrecondition",
334// (builder, model, c) -> builder.clause((f) -> List.of(
335// classModelView.call(model),
336// classElementView.call(c),
337// featuresView.call(model, f),
338// not(encapsulatesView.call(c, f))
339// )));
340//
341// TransformationRule deleteEmptyClassRule = new TransformationRule("DeleteEmptyClass0",
342// deleteEmptyClassPrecondition,
343// (model) -> {
344// var classesInterpretation = model.getInterpretation(classes);
345// var classElementInterpretation = model.getInterpretation(classElement);
346// return ((Tuple activation) -> {
347// var modelElement = activation.get(0);
348// var classElement = activation.get(1);
349//
350// classesInterpretation.put(Tuple.of(modelElement, classElement), false);
351// classElementInterpretation.put(Tuple.of(classElement), false);
352// });
353// },
354// 0L);
355//
356// var store = ModelStore.builder()
357// .symbols(classModel, classElement, feature, isEncapsulatedBy, encapsulates, classes, features)
358// .with(ViatraModelQueryAdapter.builder()
359// .queries(deleteEmptyClassPrecondition))
360// .with(DesignSpaceExplorationAdapter.builder()
361// .strategy(new DepthFirstStrategy().withDepthLimit(0)))
362// .build();
363//
364// var model = store.createEmptyModel();
365// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
366// deleteEmptyClassRule.prepare(model, queryEngine);
367//
368// var classModelInterpretation = model.getInterpretation(classModel);
369// var classElementInterpretation = model.getInterpretation(classElement);
370// var featureInterpretation = model.getInterpretation(feature);
371// var featuresInterpretation = model.getInterpretation(features);
372// var classesInterpretation = model.getInterpretation(classes);
373//
374// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
375// var newModel = dseAdapter.createObject();
376// var newModelId = newModel.get(0);
377// var newClass1 = dseAdapter.createObject();
378// var newClass1Id = newClass1.get(0);
379// var newClass2 = dseAdapter.createObject();
380// var newClass2Id = newClass2.get(0);
381// var newField = dseAdapter.createObject();
382// var newFieldId = newField.get(0);
383//
384// classModelInterpretation.put(newModel, true);
385// classElementInterpretation.put(newClass1, true);
386// classElementInterpretation.put(newClass2, true);
387// featureInterpretation.put(newField, true);
388// classesInterpretation.put(Tuple.of(newModelId, newClass1Id), true);
389// classesInterpretation.put(Tuple.of(newModelId, newClass2Id), true);
390// featuresInterpretation.put(Tuple.of(newModelId, newFieldId), true);
391//
392// queryEngine.flushChanges();
393//
394// assertResults(Map.of(
395// Tuple.of(newModelId, newClass1Id), true,
396// Tuple.of(newModelId, newClass2Id), true
397// ), deleteEmptyClassRule.getAllActivationsAsResultSet());
398//
399// deleteEmptyClassRule.fireRandomActivation();
400//
401// assertResults(Map.of(
402// Tuple.of(newModelId, newClass1Id), true,
403// Tuple.of(newModelId, newClass2Id), false
404// ), deleteEmptyClassRule.getAllActivationsAsResultSet());
405//
406// deleteEmptyClassRule.fireRandomActivation();
407//
408// assertResults(Map.of(
409// Tuple.of(newModelId, newClass1Id), false,
410// Tuple.of(newModelId, newClass2Id), false
411// ), deleteEmptyClassRule.getAllActivationsAsResultSet());
412//
413// }
414}
diff --git a/subprojects/store-dse/src/test/java/tools/refinery/store/dse/tests/QueryAssertions.java b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/tests/QueryAssertions.java
new file mode 100644
index 00000000..f0a20720
--- /dev/null
+++ b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/tests/QueryAssertions.java
@@ -0,0 +1,57 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.tests;
7
8import org.junit.jupiter.api.function.Executable;
9import tools.refinery.store.query.resultset.ResultSet;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.*;
13
14import static org.hamcrest.MatcherAssert.assertThat;
15import static org.hamcrest.Matchers.is;
16import static org.hamcrest.Matchers.nullValue;
17import static org.junit.jupiter.api.Assertions.assertAll;
18
19public final class QueryAssertions {
20 private QueryAssertions() {
21 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
22 }
23
24 public static <T> void assertNullableResults(Map<Tuple, Optional<T>> expected, ResultSet<T> resultSet) {
25 var nullableValuesMap = new LinkedHashMap<Tuple, T>(expected.size());
26 for (var entry : expected.entrySet()) {
27 nullableValuesMap.put(entry.getKey(), entry.getValue().orElse(null));
28 }
29 assertResults(nullableValuesMap, resultSet);
30 }
31
32 public static <T> void assertResults(Map<Tuple, T> expected, ResultSet<T> resultSet) {
33 var defaultValue = resultSet.getCanonicalQuery().defaultValue();
34 var filteredExpected = new LinkedHashMap<Tuple, T>();
35 var executables = new ArrayList<Executable>();
36 for (var entry : expected.entrySet()) {
37 var key = entry.getKey();
38 var value = entry.getValue();
39 if (!Objects.equals(value, defaultValue)) {
40 filteredExpected.put(key, value);
41 }
42 executables.add(() -> assertThat("value for key " + key,resultSet.get(key), is(value)));
43 }
44 executables.add(() -> assertThat("results size", resultSet.size(), is(filteredExpected.size())));
45
46 var actual = new LinkedHashMap<Tuple, T>();
47 var cursor = resultSet.getAll();
48 while (cursor.move()) {
49 var key = cursor.getKey();
50 var previous = actual.put(key, cursor.getValue());
51 assertThat("duplicate value for key " + key, previous, nullValue());
52 }
53 executables.add(() -> assertThat("results cursor", actual, is(filteredExpected)));
54
55 assertAll(executables);
56 }
57}
diff --git a/subprojects/store-dse/src/test/java/tools/refinery/store/dse/transition/statespace/internal/ActivationUnitTest.java b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/transition/statespace/internal/ActivationUnitTest.java
new file mode 100644
index 00000000..e7960a06
--- /dev/null
+++ b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/transition/statespace/internal/ActivationUnitTest.java
@@ -0,0 +1,72 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.statespace.internal;
7
8import org.junit.jupiter.api.Assertions;
9import org.junit.jupiter.params.ParameterizedTest;
10import org.junit.jupiter.params.provider.MethodSource;
11
12import java.util.stream.Stream;
13
14class ActivationUnitTest {
15 private final static int SMALL_SIZE = 5;
16
17 private static Stream<ActivationStoreEntry> entries() {
18 return Stream.of(
19 new ActivationStoreBitVectorEntry(SMALL_SIZE),
20 new ActivationStoreListEntry(SMALL_SIZE));
21 }
22
23 void addTest(ActivationStoreEntry entry, int elementsAdded) {
24 Assertions.assertEquals(elementsAdded,entry.getNumberOfVisitedActivations());
25 Assertions.assertEquals(SMALL_SIZE-elementsAdded,entry.getNumberOfUnvisitedActivations());
26 }
27
28 @ParameterizedTest
29 @MethodSource("entries")
30 void testDifferent(ActivationStoreEntry entry) {
31 int elementsAdded = 0;
32 addTest(entry,elementsAdded);
33 Assertions.assertEquals(2, entry.getAndAddActivationAfter(2));
34 addTest(entry,++elementsAdded);
35 Assertions.assertEquals(3,entry.getAndAddActivationAfter(3));
36 addTest(entry,++elementsAdded);
37 Assertions.assertEquals(1,entry.getAndAddActivationAfter(1));
38 addTest(entry,++elementsAdded);
39 Assertions.assertEquals(4,entry.getAndAddActivationAfter(4));
40 addTest(entry,++elementsAdded);
41 Assertions.assertEquals(0,entry.getAndAddActivationAfter(0));
42 addTest(entry,++elementsAdded);
43 }
44
45 @ParameterizedTest
46 @MethodSource("entries")
47 void testSame(ActivationStoreEntry entry) {
48 int elementsAdded = 0;
49 addTest(entry,elementsAdded);
50 entry.getAndAddActivationAfter(2);
51 addTest(entry,++elementsAdded);
52 entry.getAndAddActivationAfter(2);
53 addTest(entry,++elementsAdded);
54 entry.getAndAddActivationAfter(2);
55 addTest(entry,++elementsAdded);
56 entry.getAndAddActivationAfter(2);
57 addTest(entry,++elementsAdded);
58 entry.getAndAddActivationAfter(2);
59 addTest(entry,++elementsAdded);
60 }
61
62 @ParameterizedTest
63 @MethodSource("entries")
64 void testFilling(ActivationStoreEntry entry) {
65 int elementsAdded = 0;
66 while(elementsAdded < SMALL_SIZE) {
67 entry.getAndAddActivationAfter(2);
68 elementsAdded++;
69 }
70 Assertions.assertThrows(IllegalArgumentException.class,()-> entry.getAndAddActivationAfter(2));
71 }
72}
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java
index 5a484119..3f8c060a 100644
--- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java
+++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java
@@ -6,7 +6,6 @@
6package tools.refinery.store.query.viatra; 6package tools.refinery.store.query.viatra;
7 7
8import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; 8import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
9import org.junit.jupiter.api.Disabled;
10import org.junit.jupiter.api.Test; 9import org.junit.jupiter.api.Test;
11import tools.refinery.store.model.ModelStore; 10import tools.refinery.store.model.ModelStore;
12import tools.refinery.store.query.ModelQueryAdapter; 11import tools.refinery.store.query.ModelQueryAdapter;
@@ -283,7 +282,6 @@ class QueryTransactionTest {
283 assertResults(Map.of(Tuple.of(0), false), queryResultSet); 282 assertResults(Map.of(Tuple.of(0), false), queryResultSet);
284 } 283 }
285 284
286 @Disabled("TODO Fix DiffCursor")
287 @Test 285 @Test
288 void commitAfterFlushTest() { 286 void commitAfterFlushTest() {
289 var store = ModelStore.builder() 287 var store = ModelStore.builder()
@@ -332,7 +330,6 @@ class QueryTransactionTest {
332 ), predicateResultSet); 330 ), predicateResultSet);
333 } 331 }
334 332
335 @Disabled("TODO Fix DiffCursor")
336 @Test 333 @Test
337 void commitWithoutFlushTest() { 334 void commitWithoutFlushTest() {
338 var store = ModelStore.builder() 335 var store = ModelStore.builder()
diff --git a/subprojects/store/src/jmh/java/tools/refinery/store/map/benchmarks/ImmutablePutExecutionPlan.java b/subprojects/store/src/jmh/java/tools/refinery/store/map/benchmarks/ImmutablePutExecutionPlan.java
index edd4f53e..4708e6d3 100644
--- a/subprojects/store/src/jmh/java/tools/refinery/store/map/benchmarks/ImmutablePutExecutionPlan.java
+++ b/subprojects/store/src/jmh/java/tools/refinery/store/map/benchmarks/ImmutablePutExecutionPlan.java
@@ -5,12 +5,13 @@
5 */ 5 */
6package tools.refinery.store.map.benchmarks; 6package tools.refinery.store.map.benchmarks;
7 7
8import java.util.Objects;
8import java.util.Random; 9import java.util.Random;
9 10
10import tools.refinery.store.map.ContinousHashProvider; 11import tools.refinery.store.map.ContinuousHashProvider;
11import tools.refinery.store.map.VersionedMapStore; 12import tools.refinery.store.map.VersionedMapStore;
12import tools.refinery.store.map.VersionedMapStoreImpl; 13import tools.refinery.store.map.internal.state.VersionedMapStoreStateImpl;
13import tools.refinery.store.map.internal.VersionedMapImpl; 14import tools.refinery.store.map.internal.state.VersionedMapStateImpl;
14import tools.refinery.store.map.tests.utils.MapTestEnvironment; 15import tools.refinery.store.map.tests.utils.MapTestEnvironment;
15 16
16import org.openjdk.jmh.annotations.Level; 17import org.openjdk.jmh.annotations.Level;
@@ -35,17 +36,17 @@ public class ImmutablePutExecutionPlan {
35 36
36 private String[] values; 37 private String[] values;
37 38
38 private ContinousHashProvider<Integer> hashProvider = MapTestEnvironment.prepareHashProvider(false); 39 private ContinuousHashProvider<Integer> hashProvider = MapTestEnvironment.prepareHashProvider(false);
39 40
40 @Setup(Level.Trial) 41 @Setup(Level.Trial)
41 public void setUpTrial() { 42 public void setUpTrial() {
42 random = new Random(); 43 random = new Random();
43 values = MapTestEnvironment.prepareValues(nValues); 44 values = MapTestEnvironment.prepareValues(nValues, true);
44 } 45 }
45 46
46 public VersionedMapImpl<Integer, String> createSut() { 47 public VersionedMapStateImpl<Integer, String> createSut() {
47 VersionedMapStore<Integer, String> store = new VersionedMapStoreImpl<Integer, String>(hashProvider, values[0]); 48 VersionedMapStore<Integer, String> store = new VersionedMapStoreStateImpl<>(hashProvider, values[0]);
48 return (VersionedMapImpl<Integer, String>) store.createMap(); 49 return (VersionedMapStateImpl<Integer, String>) store.createMap();
49 } 50 }
50 51
51 public Integer nextKey() { 52 public Integer nextKey() {
@@ -53,7 +54,7 @@ public class ImmutablePutExecutionPlan {
53 } 54 }
54 55
55 public boolean isDefault(String value) { 56 public boolean isDefault(String value) {
56 return value == values[0]; 57 return Objects.equals(value,values[0]);
57 } 58 }
58 59
59 public String nextValue() { 60 public String nextValue() {
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/AnyVersionedMap.java b/subprojects/store/src/main/java/tools/refinery/store/map/AnyVersionedMap.java
index 25fc91e6..01099eb0 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/AnyVersionedMap.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/AnyVersionedMap.java
@@ -42,4 +42,9 @@ public sealed interface AnyVersionedMap extends Versioned permits VersionedMap {
42 @SuppressWarnings("squid:S1133") 42 @SuppressWarnings("squid:S1133")
43 @Deprecated(since = "0.0.0") 43 @Deprecated(since = "0.0.0")
44 boolean equals(Object obj); 44 boolean equals(Object obj);
45
46 /**
47 * Checks the integrity of the map, and throws an exception if an inconsistency is detected.
48 */
49 void checkIntegrity();
45} 50}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/ContinousHashProvider.java b/subprojects/store/src/main/java/tools/refinery/store/map/ContinuousHashProvider.java
index 8e451230..abc044d0 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/ContinousHashProvider.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/ContinuousHashProvider.java
@@ -5,17 +5,17 @@
5 */ 5 */
6package tools.refinery.store.map; 6package tools.refinery.store.map;
7 7
8import tools.refinery.store.map.internal.Node; 8import tools.refinery.store.map.internal.state.Node;
9 9
10/** 10/**
11 * A class representing an equivalence relation for a type {@code K} with a 11 * A class representing an equivalence relation for a type {@code K} with a
12 * continuous hash function. 12 * continuous hash function.
13 * 13 *
14 * @author Oszkar Semerath 14 * @author Oszkar Semerath
15 * 15 *
16 * @param <K> Target java type. 16 * @param <K> Target java type.
17 */ 17 */
18public interface ContinousHashProvider<K> { 18public interface ContinuousHashProvider<K> {
19 public static final int EFFECTIVE_BITS = Node.EFFECTIVE_BITS; 19 public static final int EFFECTIVE_BITS = Node.EFFECTIVE_BITS;
20 public static final int EFFECTIVE_BIT_MASK = (1 << (EFFECTIVE_BITS)) - 1; 20 public static final int EFFECTIVE_BIT_MASK = (1 << (EFFECTIVE_BITS)) - 1;
21 21
@@ -38,9 +38,9 @@ public interface ContinousHashProvider<K> {
38 * {@link #EFFECTIVE_BITS} 38 * {@link #EFFECTIVE_BITS}
39 * </ul> 39 * </ul>
40 * Check {@link #equals} for further details. 40 * Check {@link #equals} for further details.
41 * 41 *
42 * @param key The target data object. 42 * @param key The target data object.
43 * @param index The depth of the the hash code. Needs to be non-negative. 43 * @param index The depth of the hash code. Needs to be non-negative.
44 * @return A hash code. 44 * @return A hash code.
45 */ 45 */
46 public int getHash(K key, int index); 46 public int getHash(K key, int index);
@@ -53,7 +53,7 @@ public interface ContinousHashProvider<K> {
53 if (key1.equals(key2)) { 53 if (key1.equals(key2)) {
54 return 0; 54 return 0;
55 } else { 55 } else {
56 for (int i = 0; i < ContinousHashProvider.MAX_PRACTICAL_DEPTH; i++) { 56 for (int i = 0; i < ContinuousHashProvider.MAX_PRACTICAL_DEPTH; i++) {
57 int hash1 = getEffectiveHash(key1, i); 57 int hash1 = getEffectiveHash(key1, i);
58 int hash2 = getEffectiveHash(key2, i); 58 int hash2 = getEffectiveHash(key2, i);
59 for(int j = 0; j<Integer.SIZE/Node.BRANCHING_FACTOR_BITS; j++) { 59 for(int j = 0; j<Integer.SIZE/Node.BRANCHING_FACTOR_BITS; j++) {
@@ -68,7 +68,7 @@ public interface ContinousHashProvider<K> {
68 } 68 }
69 throw new IllegalArgumentException("Two different keys (" + key1 + " and " + key2 69 throw new IllegalArgumentException("Two different keys (" + key1 + " and " + key2
70 + ") have the same hashcode over the practical depth limitation (" 70 + ") have the same hashcode over the practical depth limitation ("
71 + ContinousHashProvider.MAX_PRACTICAL_DEPTH + ")!"); 71 + ContinuousHashProvider.MAX_PRACTICAL_DEPTH + ")!");
72 } 72 }
73 } 73 }
74} 74}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/IteratorAsCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/IteratorAsCursor.java
new file mode 100644
index 00000000..29b03edf
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/IteratorAsCursor.java
@@ -0,0 +1,64 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map;
7
8import java.util.Iterator;
9import java.util.Map;
10import java.util.Map.Entry;
11import java.util.Set;
12
13public class IteratorAsCursor<K, V> implements Cursor<K, V> {
14 final Iterator<Entry<K, V>> iterator;
15 final VersionedMap<K, V> source;
16
17 private boolean terminated;
18 private K key;
19 private V value;
20
21 public IteratorAsCursor(VersionedMap<K, V> source, Map<K, V> current) {
22 this.iterator = current.entrySet().iterator();
23 this.source = source;
24 }
25
26 @Override
27 public K getKey() {
28 return key;
29 }
30
31 @Override
32 public V getValue() {
33 return value;
34 }
35
36 @Override
37 public boolean isTerminated() {
38 return terminated;
39 }
40
41 @Override
42 public boolean move() {
43 terminated = !iterator.hasNext();
44 if (terminated) {
45 this.key = null;
46 this.value = null;
47 } else {
48 Entry<K, V> next = iterator.next();
49 this.key = next.getKey();
50 this.value = next.getValue();
51 }
52 return !terminated;
53 }
54
55 @Override
56 public boolean isDirty() {
57 return false;
58 }
59
60 @Override
61 public Set<AnyVersionedMap> getDependingMaps() {
62 return Set.of(this.source);
63 }
64}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/Version.java b/subprojects/store/src/main/java/tools/refinery/store/map/Version.java
new file mode 100644
index 00000000..fa2734e4
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/Version.java
@@ -0,0 +1,26 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map;
7
8/**
9 * Interface denoting versions of {@link Versioned}.
10 */
11public interface Version {
12 /**
13 * Hashcode should be updated in accordance with equals.
14 * @return a hashcode of the object.
15 */
16 int hashCode();
17
18 /**
19 * Equivalence of two {@link Version}. This equivalence must satisfy the following constraint (in addition to the
20 * constraints of {@link Object#equals(Object)}: if {@code v1} and {@code v2} are {@link Version}s, and {@code v1
21 * .equals(v2)}, then {@code versioned.restore(v1)} must be {@code equals} to {@code versioned.restore(v2)}.
22 * @param o the other object.
23 * @return weather the two versions are equals.
24 */
25 boolean equals(Object o);
26}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/Versioned.java b/subprojects/store/src/main/java/tools/refinery/store/map/Versioned.java
index 55720db3..da12b0a9 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/Versioned.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/Versioned.java
@@ -5,8 +5,20 @@
5 */ 5 */
6package tools.refinery.store.map; 6package tools.refinery.store.map;
7 7
8/**
9 * Object that can save and restore its state.
10 */
8public interface Versioned { 11public interface Versioned {
9 public long commit(); 12 /**
10 //maybe revert()? 13 * Saves the state of the object.
11 public void restore(long state); 14 * @return an object that marks the version of the object at the time the function was called.
15 */
16 Version commit();
17
18 /**
19 * Restores the state of the object.
20 * @param state a {@link Version} object that marks the version. The state must be a {@link Version} object
21 * returned by a previous {@link #commit()}!
22 */
23 void restore(Version state);
12} 24}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMap.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMap.java
index 9bbde24d..28194b58 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMap.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMap.java
@@ -6,6 +6,8 @@
6package tools.refinery.store.map; 6package tools.refinery.store.map;
7 7
8public non-sealed interface VersionedMap<K, V> extends AnyVersionedMap { 8public non-sealed interface VersionedMap<K, V> extends AnyVersionedMap {
9 V getDefaultValue();
10
9 V get(K key); 11 V get(K key);
10 12
11 Cursor<K, V> getAll(); 13 Cursor<K, V> getAll();
@@ -14,5 +16,5 @@ public non-sealed interface VersionedMap<K, V> extends AnyVersionedMap {
14 16
15 void putAll(Cursor<K, V> cursor); 17 void putAll(Cursor<K, V> cursor);
16 18
17 DiffCursor<K, V> getDiffCursor(long state); 19 DiffCursor<K, V> getDiffCursor(Version state);
18} 20}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java
index 5aafa338..55cf08a5 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java
@@ -5,15 +5,17 @@
5 */ 5 */
6package tools.refinery.store.map; 6package tools.refinery.store.map;
7 7
8import java.util.Set; 8import tools.refinery.store.map.internal.VersionedMapStoreFactoryBuilderImpl;
9 9
10public interface VersionedMapStore<K, V> { 10public interface VersionedMapStore<K, V> {
11
12 public VersionedMap<K, V> createMap();
13 11
14 public VersionedMap<K, V> createMap(long state); 12 VersionedMap<K, V> createMap();
15
16 public Set<Long> getStates();
17 13
18 public DiffCursor<K,V> getDiffCursor(long fromState, long toState); 14 VersionedMap<K, V> createMap(Version state);
19} \ No newline at end of file 15
16 DiffCursor<K,V> getDiffCursor(Version fromState, Version toState);
17
18 static <K,V> VersionedMapStoreFactoryBuilder<K,V> builder() {
19 return new VersionedMapStoreFactoryBuilderImpl<>();
20 }
21}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactory.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactory.java
new file mode 100644
index 00000000..baf6ab50
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactory.java
@@ -0,0 +1,24 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map;
7
8import java.util.List;
9
10public interface VersionedMapStoreFactory<K,V> {
11 /**
12 * Constructs a new instance of {@link VersionedMap}.
13 * @return The new instance.
14 */
15 VersionedMapStore<K,V> createOne();
16
17 /**
18 * Constructs a group of {@link VersionedMap}s with the same configuration. If possible, the stores share
19 * resources with each other.
20 * @param amount The amount of new instances to be created.
21 * @return A list of new stores with the given number of elements.
22 */
23 List<VersionedMapStore<K, V>> createGroup(int amount);
24}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java
new file mode 100644
index 00000000..0ac196f2
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map;
7
8public interface VersionedMapStoreFactoryBuilder<K,V> {
9 enum StoreStrategy {
10 STATE, DELTA
11 }
12
13 enum DeltaTransactionStrategy {
14 LIST, SET
15 }
16
17 enum SharingStrategy {
18 NO_NODE_CACHE, SHARED_NODE_CACHE, SHARED_NODE_CACHE_IN_GROUP
19 }
20
21 VersionedMapStoreFactoryBuilder<K,V> defaultValue(V defaultValue);
22 VersionedMapStoreFactoryBuilder<K,V> strategy(StoreStrategy strategy);
23 VersionedMapStoreFactoryBuilder<K,V> versionFreeing(boolean enabled);
24 VersionedMapStoreFactoryBuilder<K,V> stateBasedImmutableWhenCommitting(boolean transformToImmutable);
25 VersionedMapStoreFactoryBuilder<K,V> stateBasedSharingStrategy(SharingStrategy sharingStrategy);
26 VersionedMapStoreFactoryBuilder<K,V> stateBasedHashProvider(ContinuousHashProvider<K> hashProvider);
27 VersionedMapStoreFactoryBuilder<K,V> deltaTransactionStrategy(DeltaTransactionStrategy deltaStrategy);
28
29 VersionedMapStoreFactory<K,V> build();
30}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java
deleted file mode 100644
index aade4aeb..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java
+++ /dev/null
@@ -1,132 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map;
7
8import tools.refinery.store.map.internal.ImmutableNode;
9import tools.refinery.store.map.internal.MapDiffCursor;
10import tools.refinery.store.map.internal.Node;
11import tools.refinery.store.map.internal.VersionedMapImpl;
12
13import java.util.*;
14
15public class VersionedMapStoreImpl<K, V> implements VersionedMapStore<K, V> {
16 // Configuration
17 private final boolean immutableWhenCommitting;
18
19 // Static data
20 protected final ContinousHashProvider<K> hashProvider;
21 protected final V defaultValue;
22
23 // Dynamic data
24 protected final Map<Long, ImmutableNode<K, V>> states = new HashMap<>();
25 protected final Map<Node<K, V>, ImmutableNode<K, V>> nodeCache;
26 protected long nextID = 0;
27
28 public VersionedMapStoreImpl(ContinousHashProvider<K> hashProvider, V defaultValue,
29 VersionedMapStoreConfiguration config) {
30 this.immutableWhenCommitting = config.isImmutableWhenCommitting();
31 this.hashProvider = hashProvider;
32 this.defaultValue = defaultValue;
33 if (config.isSharedNodeCacheInStore()) {
34 nodeCache = new HashMap<>();
35 } else {
36 nodeCache = null;
37 }
38 }
39
40 private VersionedMapStoreImpl(ContinousHashProvider<K> hashProvider, V defaultValue,
41 Map<Node<K, V>, ImmutableNode<K, V>> nodeCache, VersionedMapStoreConfiguration config) {
42 this.immutableWhenCommitting = config.isImmutableWhenCommitting();
43 this.hashProvider = hashProvider;
44 this.defaultValue = defaultValue;
45 this.nodeCache = nodeCache;
46 }
47
48 public VersionedMapStoreImpl(ContinousHashProvider<K> hashProvider, V defaultValue) {
49 this(hashProvider, defaultValue, new VersionedMapStoreConfiguration());
50 }
51
52 public static <K, V> List<VersionedMapStore<K, V>> createSharedVersionedMapStores(int amount,
53 ContinousHashProvider<K> hashProvider, V defaultValue,
54 VersionedMapStoreConfiguration config) {
55 List<VersionedMapStore<K, V>> result = new ArrayList<>(amount);
56 if (config.isSharedNodeCacheInStoreGroups()) {
57 Map<Node<K, V>, ImmutableNode<K, V>> nodeCache;
58 if (config.isSharedNodeCacheInStore()) {
59 nodeCache = new HashMap<>();
60 } else {
61 nodeCache = null;
62 }
63 for (int i = 0; i < amount; i++) {
64 result.add(new VersionedMapStoreImpl<>(hashProvider, defaultValue, nodeCache, config));
65 }
66 } else {
67 for (int i = 0; i < amount; i++) {
68 result.add(new VersionedMapStoreImpl<>(hashProvider, defaultValue, config));
69 }
70 }
71 return result;
72 }
73
74 public static <K, V> List<VersionedMapStore<K, V>> createSharedVersionedMapStores(int amount,
75 ContinousHashProvider<K> hashProvider, V defaultValue) {
76 return createSharedVersionedMapStores(amount, hashProvider, defaultValue, new VersionedMapStoreConfiguration());
77 }
78
79 @Override
80 public synchronized Set<Long> getStates() {
81 return new HashSet<>(states.keySet());
82 }
83
84 @Override
85 public VersionedMap<K, V> createMap() {
86 return new VersionedMapImpl<>(this, hashProvider, defaultValue);
87 }
88
89 @Override
90 public VersionedMap<K, V> createMap(long state) {
91 ImmutableNode<K, V> data = revert(state);
92 return new VersionedMapImpl<>(this, hashProvider, defaultValue, data);
93 }
94
95 public synchronized ImmutableNode<K, V> revert(long state) {
96 if (states.containsKey(state)) {
97 return states.get(state);
98 } else {
99 ArrayList<Long> existingKeys = new ArrayList<>(states.keySet());
100 Collections.sort(existingKeys);
101 throw new IllegalArgumentException("Store does not contain state " + state + "! Avaliable states: "
102 + Arrays.toString(existingKeys.toArray()));
103 }
104 }
105
106 public synchronized long commit(Node<K, V> data, VersionedMapImpl<K, V> mapToUpdateRoot) {
107 ImmutableNode<K, V> immutable;
108 if (data != null) {
109 immutable = data.toImmutable(this.nodeCache);
110 } else {
111 immutable = null;
112 }
113
114 if (nextID == Long.MAX_VALUE)
115 throw new IllegalStateException("Map store run out of Id-s");
116 long id = nextID++;
117 this.states.put(id, immutable);
118 if (this.immutableWhenCommitting) {
119 mapToUpdateRoot.setRoot(immutable);
120 }
121 return id;
122 }
123
124 @Override
125 public DiffCursor<K, V> getDiffCursor(long fromState, long toState) {
126 VersionedMap<K, V> map1 = createMap(fromState);
127 VersionedMap<K, V> map2 = createMap(toState);
128 Cursor<K, V> cursor1 = map1.getAll();
129 Cursor<K, V> cursor2 = map2.getAll();
130 return new MapDiffCursor<>(this.hashProvider, this.defaultValue, cursor1, cursor2);
131 }
132}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/HashClash.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/HashClash.java
deleted file mode 100644
index 61b3d1b8..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/HashClash.java
+++ /dev/null
@@ -1,23 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal;
7
8enum HashClash {
9 /**
10 * Not stuck.
11 */
12 NONE,
13
14 /**
15 * Clashed, next we should return the key of cursor 1.
16 */
17 STUCK_CURSOR_1,
18
19 /**
20 * Clashed, next we should return the key of cursor 2.
21 */
22 STUCK_CURSOR_2
23}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java
deleted file mode 100644
index d31f1a05..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java
+++ /dev/null
@@ -1,229 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal;
7
8import tools.refinery.store.map.AnyVersionedMap;
9import tools.refinery.store.map.ContinousHashProvider;
10import tools.refinery.store.map.Cursor;
11import tools.refinery.store.map.DiffCursor;
12
13import java.util.Set;
14import java.util.stream.Collectors;
15import java.util.stream.Stream;
16
17/**
18 * A cursor representing the difference between two states of a map.
19 *
20 * @author Oszkar Semerath
21 *
22 */
23public class MapDiffCursor<K, V> implements DiffCursor<K, V>, Cursor<K, V> {
24 /**
25 * Default nodeId representing missing elements.
26 */
27 private final V defaultValue;
28 private final MapCursor<K, V> cursor1;
29 private final MapCursor<K, V> cursor2;
30 private final ContinousHashProvider<? super K> hashProvider;
31
32 // Values
33 private K key;
34 private V fromValue;
35 private V toValue;
36
37 // State
38 /**
39 * Positive number if cursor 1 is behind, negative number if cursor 2 is behind,
40 * and 0 if they are at the same position.
41 */
42 private int cursorRelation;
43 private HashClash hashClash = HashClash.NONE;
44
45 public MapDiffCursor(ContinousHashProvider<? super K> hashProvider, V defaultValue, Cursor<K, V> cursor1,
46 Cursor<K, V> cursor2) {
47 super();
48 this.hashProvider = hashProvider;
49 this.defaultValue = defaultValue;
50 this.cursor1 = (MapCursor<K, V>) cursor1;
51 this.cursor2 = (MapCursor<K, V>) cursor2;
52 }
53
54 @Override
55 public K getKey() {
56 return key;
57 }
58
59 @Override
60 public V getFromValue() {
61 return fromValue;
62 }
63
64 @Override
65 public V getToValue() {
66 return toValue;
67 }
68
69 @Override
70 public V getValue() {
71 return getToValue();
72 }
73
74 public boolean isTerminated() {
75 return cursor1.isTerminated() && cursor2.isTerminated();
76 }
77
78 @Override
79 public boolean isDirty() {
80 return this.cursor1.isDirty() || this.cursor2.isDirty();
81 }
82
83 @Override
84 public Set<AnyVersionedMap> getDependingMaps() {
85 return Stream.concat(cursor1.getDependingMaps().stream(), cursor2.getDependingMaps().stream())
86 .map(AnyVersionedMap.class::cast)
87 .collect(Collectors.toUnmodifiableSet());
88 }
89
90 protected void updateState() {
91 if (!isTerminated()) {
92 this.cursorRelation = MapCursor.compare(cursor1, cursor2);
93 if (cursorRelation > 0 || cursor2.isTerminated()) {
94 this.key = cursor1.getKey();
95 this.fromValue = cursor1.getValue();
96 this.toValue = defaultValue;
97 } else if (cursorRelation < 0 || cursor1.isTerminated()) {
98 this.key = cursor2.getKey();
99 this.fromValue = defaultValue;
100 this.toValue = cursor1.getValue();
101 } else {
102 // cursor1 = cursor2
103 if (cursor1.getKey().equals(cursor2.getKey())) {
104 this.key = cursor1.getKey();
105 this.fromValue = cursor1.getValue();
106 this.toValue = defaultValue;
107 } else {
108 resolveHashClashWithFirstEntry();
109 }
110 }
111 }
112 }
113
114 protected void resolveHashClashWithFirstEntry() {
115 int compareResult = this.hashProvider.compare(cursor1.key, cursor2.key);
116 if (compareResult < 0) {
117 this.hashClash = HashClash.STUCK_CURSOR_2;
118 this.cursorRelation = 0;
119 this.key = cursor1.key;
120 this.fromValue = cursor1.value;
121 this.toValue = defaultValue;
122 } else if (compareResult > 0) {
123 this.hashClash = HashClash.STUCK_CURSOR_1;
124 this.cursorRelation = 0;
125 this.key = cursor2.key;
126 this.fromValue = defaultValue;
127 this.toValue = cursor2.value;
128 } else {
129 throw new IllegalArgumentException("Inconsistent compare result for diffcursor");
130 }
131 }
132
133 protected boolean isInHashClash() {
134 return this.hashClash != HashClash.NONE;
135 }
136
137 protected void resolveHashClashWithSecondEntry() {
138 switch (this.hashClash) {
139 case STUCK_CURSOR_1:
140 this.hashClash = HashClash.NONE;
141 this.cursorRelation = 0;
142 this.key = cursor1.key;
143 this.fromValue = cursor1.value;
144 this.toValue = defaultValue;
145 break;
146 case STUCK_CURSOR_2:
147 this.hashClash = HashClash.NONE;
148 this.cursorRelation = 0;
149 this.key = cursor2.key;
150 this.fromValue = defaultValue;
151 this.toValue = cursor2.value;
152 break;
153 default:
154 throw new IllegalArgumentException("Inconsistent compare result for diffcursor");
155 }
156 }
157
158 protected boolean sameValues() {
159 if (this.fromValue == null) {
160 return this.toValue == null;
161 } else {
162 return this.fromValue.equals(this.toValue);
163 }
164 }
165
166 protected boolean moveOne() {
167 if (isTerminated()) {
168 return false;
169 }
170 if (this.cursorRelation > 0 || cursor2.isTerminated()) {
171 return cursor1.move();
172 } else if (this.cursorRelation < 0 || cursor1.isTerminated()) {
173 return cursor2.move();
174 } else {
175 boolean moved1 = cursor1.move();
176 boolean moved2 = cursor2.move();
177 return moved1 && moved2;
178 }
179 }
180
181 private boolean skipNode() {
182 if (isTerminated()) {
183 throw new IllegalStateException("DiffCursor tries to skip when terminated!");
184 }
185 boolean update1 = cursor1.skipCurrentNode();
186 boolean update2 = cursor2.skipCurrentNode();
187 updateState();
188 return update1 && update2;
189 }
190
191 protected boolean moveToConsistentState() {
192 if (!isTerminated()) {
193 boolean changed;
194 boolean lastResult = true;
195 do {
196 changed = false;
197 if (MapCursor.sameSubnode(cursor1, cursor2)) {
198 lastResult = skipNode();
199 changed = true;
200 }
201 if (sameValues()) {
202 lastResult = moveOne();
203 changed = true;
204 }
205 updateState();
206 } while (changed && !isTerminated());
207 return lastResult;
208 } else {
209 return false;
210 }
211 }
212
213 public boolean move() {
214 if (!isTerminated()) {
215 if (isInHashClash()) {
216 this.resolveHashClashWithSecondEntry();
217 return true;
218 } else {
219 if (moveOne()) {
220 return moveToConsistentState();
221 } else {
222 return false;
223 }
224 }
225
226 } else
227 return false;
228 }
229}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java
deleted file mode 100644
index 958d645f..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java
+++ /dev/null
@@ -1,90 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal;
7
8import java.util.Map;
9
10import tools.refinery.store.map.ContinousHashProvider;
11
12public abstract class Node<K,V>{
13 public static final int BRANCHING_FACTOR_BITS = 5;
14 public static final int FACTOR = 1<<BRANCHING_FACTOR_BITS;
15 protected static final int NUMBER_OF_FACTORS = Integer.SIZE / BRANCHING_FACTOR_BITS;
16 protected static final int FACTOR_MASK = FACTOR-1;
17 public static final int EFFECTIVE_BITS = BRANCHING_FACTOR_BITS * NUMBER_OF_FACTORS;
18
19 /**
20 * Calculates the index for the continuous hash.
21 * @param depth The depth of the node in the tree.
22 * @return The index of the continuous hash.
23 */
24 protected static int hashDepth(int depth) {
25 return depth/NUMBER_OF_FACTORS;
26 }
27
28 /**
29 * Calculates the which segment of a single hash should be used.
30 * @param depth The depth of the node in the tree.
31 * @return The segment of a hash code.
32 */
33 protected static int shiftDepth(int depth) {
34 return depth%NUMBER_OF_FACTORS;
35 }
36 /**
37 * Selects a segments from a complete hash for a given depth.
38 * @param hash A complete hash.
39 * @param shiftDepth The index of the segment.
40 * @return The segment as an integer.
41 */
42 protected static int hashFragment(int hash, int shiftDepth) {
43 if(shiftDepth<0 || Node.NUMBER_OF_FACTORS<shiftDepth) throw new IllegalArgumentException("Invalid shift depth! valid intervall=[0;5], input="+shiftDepth);
44 return (hash >>> shiftDepth*BRANCHING_FACTOR_BITS) & FACTOR_MASK;
45 }
46
47 /**
48 * Returns the hash code for a given depth. It may calculate new hash code, or reuse a hash code calculated for depth-1.
49 * @param key The key.
50 * @param hash Hash code for depth-1
51 * @param depth The depth.
52 * @return The new hash code.
53 */
54 protected int newHash(final ContinousHashProvider<? super K> hashProvider, K key, int hash, int depth) {
55 final int hashDepth = hashDepth(depth);
56 if(hashDepth>=ContinousHashProvider.MAX_PRACTICAL_DEPTH) {
57 throw new IllegalArgumentException("Key "+key+" have the clashing hashcode over the practical depth limitation ("+ContinousHashProvider.MAX_PRACTICAL_DEPTH+")!");
58 }
59 return depth%NUMBER_OF_FACTORS == 0?
60 hashProvider.getHash(key, hashDepth) :
61 hash;
62 }
63
64
65 public abstract V getValue(K key, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth);
66 public abstract Node<K,V> putValue(K key, V value, OldValueBox<V> old, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth);
67 public abstract long getSize();
68
69 abstract MutableNode<K, V> toMutable();
70 public abstract ImmutableNode<K, V> toImmutable(
71 Map<Node<K, V>,ImmutableNode<K, V>> cache);
72 protected abstract MutableNode<K, V> isMutable();
73 /**
74 * Moves a {@link MapCursor} to its next position.
75 * @param cursor the cursor
76 * @return Whether there was a next nodeId to move on.
77 */
78 abstract boolean moveToNext(MapCursor<K,V> cursor);
79
80 ///////// FOR printing
81 public abstract void prettyPrint(StringBuilder builder, int depth, int code);
82 @Override
83 public String toString() {
84 StringBuilder stringBuilder = new StringBuilder();
85 prettyPrint(stringBuilder, 0, -1);
86 return stringBuilder.toString();
87 }
88 public void checkIntegrity(ContinousHashProvider<? super K> hashProvider, V defaultValue, int depth) {}
89
90}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java
new file mode 100644
index 00000000..9f419ce1
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java
@@ -0,0 +1,150 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal;
7
8import tools.refinery.store.map.ContinuousHashProvider;
9import tools.refinery.store.map.VersionedMapStoreFactory;
10import tools.refinery.store.map.VersionedMapStoreFactoryBuilder;
11import tools.refinery.store.map.internal.delta.DeltaBasedVersionedMapStoreFactory;
12import tools.refinery.store.map.internal.state.StateBasedVersionedMapStoreFactory;
13
14public class VersionedMapStoreFactoryBuilderImpl<K, V> implements VersionedMapStoreFactoryBuilder<K, V> {
15
16 private boolean defaultSet = false;
17 private V defaultValue;
18 private StoreStrategy strategy = null;
19 private Boolean transformToImmutable = null;
20 private SharingStrategy sharingStrategy = null;
21 private Boolean enableVersionFreeing = null;
22 private ContinuousHashProvider<K> continuousHashProvider = null;
23 private DeltaTransactionStrategy deltaTransactionStrategy = null;
24
25 private StoreStrategy checkStrategy() {
26 StoreStrategy currentStrategy = strategy;
27 currentStrategy = mergeStrategies(currentStrategy, transformToImmutable, StoreStrategy.STATE);
28 currentStrategy = mergeStrategies(currentStrategy, sharingStrategy, StoreStrategy.STATE);
29 currentStrategy = mergeStrategies(currentStrategy, continuousHashProvider, StoreStrategy.STATE);
30 currentStrategy = mergeStrategies(currentStrategy, deltaTransactionStrategy, StoreStrategy.DELTA);
31 return currentStrategy;
32 }
33
34 private StoreStrategy mergeStrategies(StoreStrategy old, StoreStrategy newStrategy) {
35 if (old != null && newStrategy != null && old != newStrategy) {
36 throw new IllegalArgumentException("Mixed strategy parametrization in VersionedMap builder!");
37 }
38
39 if (old != null) {
40 return old;
41 } else {
42 return newStrategy;
43 }
44 }
45
46 private StoreStrategy mergeStrategies(StoreStrategy old, Object parameter, StoreStrategy newStrategy) {
47 if (parameter != null) {
48 return mergeStrategies(old, newStrategy);
49 } else {
50 return old;
51 }
52 }
53
54 @Override
55 public VersionedMapStoreFactoryBuilder<K, V> defaultValue(V defaultValue) {
56 this.defaultSet = true;
57 this.defaultValue = defaultValue;
58 return this;
59 }
60
61 @Override
62 public VersionedMapStoreFactoryBuilder<K, V> strategy(StoreStrategy strategy) {
63 this.strategy = strategy;
64 checkStrategy();
65 return this;
66 }
67
68 @Override
69 public VersionedMapStoreFactoryBuilder<K, V> versionFreeing(boolean enabled) {
70 this.enableVersionFreeing = enabled;
71 checkStrategy();
72 return this;
73 }
74
75 @Override
76 public VersionedMapStoreFactoryBuilder<K, V> stateBasedImmutableWhenCommitting(boolean transformToImmutable) {
77 this.transformToImmutable = transformToImmutable;
78 checkStrategy();
79 return this;
80 }
81
82 @Override
83 public VersionedMapStoreFactoryBuilder<K, V> stateBasedSharingStrategy(SharingStrategy sharingStrategy) {
84 this.sharingStrategy = sharingStrategy;
85 checkStrategy();
86 return this;
87 }
88
89 @Override
90 public VersionedMapStoreFactoryBuilder<K, V> stateBasedHashProvider(ContinuousHashProvider<K> hashProvider) {
91 this.continuousHashProvider = hashProvider;
92 checkStrategy();
93 return this;
94 }
95
96 @Override
97 public VersionedMapStoreFactoryBuilder<K, V> deltaTransactionStrategy(DeltaTransactionStrategy deltaTransactionStrategy) {
98 this.deltaTransactionStrategy = deltaTransactionStrategy;
99 checkStrategy();
100 return this;
101 }
102
103 private <T> T getOrDefault(T value, T defaultValue) {
104 if(value != null) {
105 return value;
106 } else {
107 return defaultValue;
108 }
109 }
110
111 @Override
112 public VersionedMapStoreFactory<K, V> build() {
113 if (!defaultSet) {
114 throw new IllegalArgumentException("Default value is missing!");
115 }
116 var strategyToUse = checkStrategy();
117 if (strategyToUse == null) {
118 return new DeltaBasedVersionedMapStoreFactory<>(defaultValue,
119 getOrDefault(deltaTransactionStrategy, DeltaTransactionStrategy.LIST));
120 }
121 return switch (strategyToUse) {
122 case STATE -> {
123 if(continuousHashProvider == null) {
124 throw new IllegalArgumentException("Continuous hash provider is missing!");
125 }
126 yield new StateBasedVersionedMapStoreFactory<>(defaultValue,
127 getOrDefault(transformToImmutable,true),
128 getOrDefault(sharingStrategy, SharingStrategy.SHARED_NODE_CACHE_IN_GROUP),
129 getOrDefault(enableVersionFreeing, true),
130 continuousHashProvider);
131 }
132 case DELTA -> new DeltaBasedVersionedMapStoreFactory<>(defaultValue,
133 getOrDefault(deltaTransactionStrategy, DeltaTransactionStrategy.LIST));
134 };
135 }
136
137 @Override
138 public String toString() {
139 return "VersionedMapStoreFactoryBuilderImpl{" +
140 "defaultSet=" + defaultSet +
141 ", defaultValue=" + defaultValue +
142 ", strategy=" + strategy +
143 ", transformToImmutable=" + transformToImmutable +
144 ", sharingStrategy=" + sharingStrategy +
145 ", enableVersionFreeing=" + enableVersionFreeing +
146 ", continuousHashProvider=" + continuousHashProvider +
147 ", deltaTransactionStrategy=" + deltaTransactionStrategy +
148 '}';
149 }
150}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaBasedVersionedMapStoreFactory.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaBasedVersionedMapStoreFactory.java
new file mode 100644
index 00000000..cedcdc0b
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaBasedVersionedMapStoreFactory.java
@@ -0,0 +1,38 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.delta;
7
8import tools.refinery.store.map.VersionedMapStore;
9import tools.refinery.store.map.VersionedMapStoreFactory;
10import tools.refinery.store.map.VersionedMapStoreFactoryBuilder;
11
12import java.util.ArrayList;
13import java.util.List;
14
15public class DeltaBasedVersionedMapStoreFactory<K, V> implements VersionedMapStoreFactory<K, V> {
16 private final V defaultValue;
17 private final boolean summarizeChanges;
18
19 public DeltaBasedVersionedMapStoreFactory(V defaultValue,
20 VersionedMapStoreFactoryBuilder.DeltaTransactionStrategy deltaTransactionStrategy) {
21 this.defaultValue = defaultValue;
22 this.summarizeChanges = deltaTransactionStrategy == VersionedMapStoreFactoryBuilder.DeltaTransactionStrategy.SET;
23 }
24
25 @Override
26 public VersionedMapStore<K, V> createOne() {
27 return new VersionedMapStoreDeltaImpl<>(summarizeChanges, defaultValue);
28 }
29
30 @Override
31 public List<VersionedMapStore<K, V>> createGroup(int amount) {
32 List<VersionedMapStore<K, V>> result = new ArrayList<>(amount);
33 for(int i=0; i<amount; i++) {
34 result.add(new VersionedMapStoreDeltaImpl<>(summarizeChanges,defaultValue));
35 }
36 return result;
37 }
38}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaDiffCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaDiffCursor.java
new file mode 100644
index 00000000..ce10b246
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaDiffCursor.java
@@ -0,0 +1,147 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.delta;
7
8import tools.refinery.store.map.AnyVersionedMap;
9import tools.refinery.store.map.DiffCursor;
10
11import java.util.Collections;
12import java.util.List;
13import java.util.Set;
14
15public class DeltaDiffCursor<K, V> implements DiffCursor<K, V> {
16 final List<MapDelta<K, V>[]> backwardTransactions;
17 final List<MapDelta<K, V>[]> forwardTransactions;
18
19 boolean started;
20 /**
21 * Denotes the direction of traversal. False means backwards, true means
22 * forward.
23 */
24 boolean direction;
25 int listIndex;
26 int arrayIndex;
27
28 public DeltaDiffCursor(List<MapDelta<K, V>[]> backwardTransactions, List<MapDelta<K, V>[]> forwardTransactions) {
29 this.backwardTransactions = backwardTransactions;
30 this.forwardTransactions = forwardTransactions;
31
32 if (!backwardTransactions.isEmpty()) {
33 direction = false;
34 listIndex = 0;
35 arrayIndex = backwardTransactions.get(listIndex).length - 1;
36 } else if (!forwardTransactions.isEmpty()) {
37 direction = true;
38 listIndex = forwardTransactions.size() - 1;
39 arrayIndex = 0;
40 } else {
41 direction = true;
42 listIndex = -1;
43 }
44 started = false;
45 }
46
47 protected MapDelta<K, V> getCurrentDelta() {
48 final List<MapDelta<K, V>[]> list;
49 if (!direction) {
50 list = this.backwardTransactions;
51 } else {
52 list = this.forwardTransactions;
53 }
54 return list.get(listIndex)[arrayIndex];
55 }
56
57 @Override
58 public K getKey() {
59 return getCurrentDelta().getKey();
60 }
61
62 @Override
63 public V getValue() {
64 return getToValue();
65 }
66
67 @Override
68 public boolean isTerminated() {
69 return this.direction && listIndex == -1;
70 }
71
72
73 @Override
74 public boolean move() {
75 if(!started) {
76 started = true;
77 return !isTerminated();
78 } else if (isTerminated()) {
79 return false;
80 } else {
81 if (this.direction) {
82 if (arrayIndex+1 < forwardTransactions.get(listIndex).length) {
83 arrayIndex++;
84 return true;
85 } else {
86 if (listIndex-1 >= 0) {
87 listIndex--;
88 arrayIndex = 0;
89 return true;
90 } else {
91 listIndex = -1;
92 return false;
93 }
94 }
95 } else {
96 if (arrayIndex > 0) {
97 arrayIndex--;
98 return true;
99 } else {
100 if (listIndex+1 < backwardTransactions.size()) {
101 listIndex++;
102 this.arrayIndex = backwardTransactions.get(listIndex).length - 1;
103 return true;
104 } else {
105 this.direction = true;
106 if (!this.forwardTransactions.isEmpty()) {
107 listIndex = forwardTransactions.size() - 1;
108 arrayIndex = 0;
109 return true;
110 } else {
111 listIndex = -1;
112 return false;
113 }
114 }
115 }
116 }
117 }
118 }
119
120 @Override
121 public boolean isDirty() {
122 return false;
123 }
124
125 @Override
126 public Set<AnyVersionedMap> getDependingMaps() {
127 return Collections.emptySet();
128 }
129
130 @Override
131 public V getFromValue() {
132 if(this.direction) {
133 return getCurrentDelta().getOldValue();
134 } else {
135 return getCurrentDelta().getNewValue();
136 }
137 }
138
139 @Override
140 public V getToValue() {
141 if(this.direction) {
142 return getCurrentDelta().getNewValue();
143 } else {
144 return getCurrentDelta().getOldValue();
145 }
146 }
147}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapDelta.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapDelta.java
new file mode 100644
index 00000000..0c0cc906
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapDelta.java
@@ -0,0 +1,20 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.delta;
7
8public record MapDelta<K, V>(K key, V oldValue, V newValue) {
9 public K getKey() {
10 return key;
11 }
12
13 public V getOldValue() {
14 return oldValue;
15 }
16
17 public V getNewValue() {
18 return newValue;
19 }
20}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapTransaction.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapTransaction.java
new file mode 100644
index 00000000..6f3fa6d7
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapTransaction.java
@@ -0,0 +1,41 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.delta;
7
8import tools.refinery.store.map.Version;
9
10import java.util.Arrays;
11import java.util.Objects;
12
13public record MapTransaction<K, V>(MapDelta<K, V>[] deltas, MapTransaction<K, V> parent, int depth) implements Version {
14
15 @Override
16 public int hashCode() {
17 final int prime = 31;
18 int result = 1;
19 result = prime * result + Arrays.hashCode(deltas);
20 result = prime * result + Objects.hash(parent, depth);
21 return result;
22 }
23
24 @Override
25 public boolean equals(Object obj) {
26 if (this == obj)
27 return true;
28 if (obj == null)
29 return false;
30 if (getClass() != obj.getClass())
31 return false;
32 @SuppressWarnings("unchecked")
33 MapTransaction<K, V> other = (MapTransaction<K, V>) obj;
34 return depth == other.depth && Objects.equals(parent, other.parent) && Arrays.equals(deltas, other.deltas);
35 }
36
37 @Override
38 public String toString() {
39 return "MapTransaction " + depth + " " + Arrays.toString(deltas);
40 }
41}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaArrayStore.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaArrayStore.java
new file mode 100644
index 00000000..1f6a9000
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaArrayStore.java
@@ -0,0 +1,36 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.delta;
7
8import java.util.ArrayList;
9import java.util.List;
10
11public class UncommittedDeltaArrayStore<K, V> implements UncommittedDeltaStore<K, V> {
12 final List<MapDelta<K, V>> uncommittedOldValues = new ArrayList<>();
13
14 @Override
15 public void processChange(K key, V oldValue, V newValue) {
16 uncommittedOldValues.add(new MapDelta<>(key, oldValue, newValue));
17 }
18
19 @Override
20 public MapDelta<K, V>[] extractDeltas() {
21 if (uncommittedOldValues.isEmpty()) {
22 return null;
23 } else {
24 @SuppressWarnings("unchecked")
25 MapDelta<K, V>[] result = uncommittedOldValues.toArray(new MapDelta[0]);
26 return result;
27 }
28 }
29
30 @Override
31 public MapDelta<K, V>[] extractAndDeleteDeltas() {
32 MapDelta<K, V>[] res = extractDeltas();
33 this.uncommittedOldValues.clear();
34 return res;
35 }
36}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaMapStore.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaMapStore.java
new file mode 100644
index 00000000..644884a6
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaMapStore.java
@@ -0,0 +1,53 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.delta;
7
8import java.util.*;
9import java.util.Map.Entry;
10
11import tools.refinery.store.map.VersionedMap;
12
13public class UncommittedDeltaMapStore<K, V> implements UncommittedDeltaStore<K, V> {
14 final VersionedMap<K, V> source;
15 final Map<K, V> uncommittedOldValues = new HashMap<>();
16
17 public UncommittedDeltaMapStore(VersionedMap<K, V> source) {
18 this.source = source;
19 }
20
21 @Override
22 public void processChange(K key, V oldValue, V newValue) {
23 if(!uncommittedOldValues.containsKey(key)) {
24 this.uncommittedOldValues.put(key,oldValue);
25 }
26 }
27
28 @Override
29 public MapDelta<K, V>[] extractDeltas() {
30 if (uncommittedOldValues.isEmpty()) {
31 return null;
32 } else {
33 @SuppressWarnings("unchecked")
34 MapDelta<K,V>[] deltas = new MapDelta[uncommittedOldValues.size()];
35 int i = 0;
36 for (Entry<K, V> entry : uncommittedOldValues.entrySet()) {
37 final K key = entry.getKey();
38 final V oldValue = entry.getValue();
39 final V newValue = source.get(key);
40 deltas[i++] = new MapDelta<>(key, oldValue, newValue);
41 }
42
43 return deltas;
44 }
45 }
46
47 @Override
48 public MapDelta<K, V>[] extractAndDeleteDeltas() {
49 MapDelta<K, V>[] res = extractDeltas();
50 this.uncommittedOldValues.clear();
51 return res;
52 }
53}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaStore.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaStore.java
new file mode 100644
index 00000000..ecd33c5f
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaStore.java
@@ -0,0 +1,29 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.delta;
7
8public interface UncommittedDeltaStore<K, V> {
9 void processChange(K key, V oldValue, V newValue);
10
11 MapDelta<K, V>[] extractDeltas();
12
13 MapDelta<K, V>[] extractAndDeleteDeltas();
14
15 default void checkIntegrity() {
16 MapDelta<K, V>[] extractedDeltas = extractDeltas();
17 if(extractedDeltas != null) {
18 for(var uncommittedOldValue : extractedDeltas) {
19 if(uncommittedOldValue == null) {
20 throw new IllegalArgumentException("Null entry in deltas!");
21 }
22 if(uncommittedOldValue.getKey() == null) {
23 throw new IllegalStateException("Null key in deltas!");
24 }
25 }
26 }
27 }
28
29}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java
new file mode 100644
index 00000000..d530ae87
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java
@@ -0,0 +1,237 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.delta;
7
8import tools.refinery.store.map.*;
9
10import java.util.*;
11
12public class VersionedMapDeltaImpl<K, V> implements VersionedMap<K, V> {
13 protected final VersionedMapStoreDeltaImpl<K, V> store;
14
15 final Map<K, V> current;
16
17 final UncommittedDeltaStore<K, V> uncommittedStore;
18 MapTransaction<K, V> previous;
19
20 protected final V defaultValue;
21
22 public VersionedMapDeltaImpl(VersionedMapStoreDeltaImpl<K, V> store, boolean summarizeChanges, V defaultValue) {
23 this.store = store;
24 this.defaultValue = defaultValue;
25
26 current = new HashMap<>();
27 if (summarizeChanges) {
28 this.uncommittedStore = new UncommittedDeltaMapStore<>(this);
29 } else {
30 this.uncommittedStore = new UncommittedDeltaArrayStore<>();
31 }
32 }
33
34 @Override
35 public V getDefaultValue() {
36 return defaultValue;
37 }
38
39 @Override
40 public Version commit() {
41 MapDelta<K, V>[] deltas = uncommittedStore.extractAndDeleteDeltas();
42 final MapTransaction<K,V> committedTransaction = this.store.appendTransaction(deltas, previous);
43 this.previous = committedTransaction;
44 return committedTransaction;
45 }
46
47 @Override
48 public void restore(Version state) {
49 // 1. restore uncommitted states
50 MapDelta<K, V>[] uncommitted = this.uncommittedStore.extractAndDeleteDeltas();
51 if (uncommitted != null) {
52 backward(uncommitted);
53 }
54
55 // 2. get common ancestor
56 final MapTransaction<K,V> parent;
57 List<MapDelta<K, V>[]> forward = new ArrayList<>();
58 if (this.previous == null) {
59 parent = this.store.getPath(state, forward);
60 this.forward(forward);
61 } else {
62 List<MapDelta<K, V>[]> backward = new ArrayList<>();
63 parent = this.store.getPath(this.previous, state, backward, forward);
64 this.backward(backward);
65 this.forward(forward);
66 }
67 this.previous = parent;
68 }
69
70 protected void forward(List<MapDelta<K, V>[]> changes) {
71 for (int i = changes.size() - 1; i >= 0; i--) {
72 forward(changes.get(i));
73 }
74 }
75
76 protected void backward(List<MapDelta<K, V>[]> changes) {
77 //Currently, this loop statement is faster.
78 //noinspection ForLoopReplaceableByForEach
79 for (int i = 0; i < changes.size(); i++) {
80 backward(changes.get(i));
81 }
82 }
83
84 protected void forward(MapDelta<K, V>[] changes) {
85 //Currently, this loop statement is faster.
86 //noinspection ForLoopReplaceableByForEach
87 for (int i = 0; i < changes.length; i++) {
88 final MapDelta<K, V> change = changes[i];
89 K key = change.getKey();
90 V newValue = change.getNewValue();
91
92 if(newValue == defaultValue) {
93 current.remove(key);
94 } else {
95 current.put(key,newValue);
96 }
97 }
98 }
99
100 protected void backward(MapDelta<K, V>[] changes) {
101 for (int i = changes.length - 1; i >= 0; i--) {
102 final MapDelta<K, V> change = changes[i];
103 K key = change.getKey();
104 V oldValue = change.oldValue();
105
106 if(oldValue == defaultValue) {
107 current.remove(key);
108 } else {
109 current.put(key,oldValue);
110 }
111 }
112 }
113
114 @Override
115 public V get(K key) {
116 return current.getOrDefault(key, defaultValue);
117 }
118
119 @Override
120 public Cursor<K, V> getAll() {
121 return new IteratorAsCursor<>(this, current);
122 }
123
124 @Override
125 public V put(K key, V value) {
126 final V oldValue;
127 if (Objects.equals(value, defaultValue)) {
128 final V res = current.remove(key);
129 if (res == null) {
130 // no changes: default > default
131 oldValue = defaultValue;
132 } else {
133 oldValue = res;
134 }
135 } else {
136 final var mapValue = current.put(key, value);
137 if (mapValue == null) {
138 oldValue = defaultValue;
139 } else {
140 oldValue = mapValue;
141 }
142 }
143 if(!Objects.equals(oldValue,value)) {
144 uncommittedStore.processChange(key, oldValue, value);
145 }
146 return oldValue;
147 }
148
149 @Override
150 public void putAll(Cursor<K, V> cursor) {
151 if (cursor.getDependingMaps().contains(this)) {
152 List<K> keys = new ArrayList<>();
153 List<V> values = new ArrayList<>();
154 while (cursor.move()) {
155 keys.add(cursor.getKey());
156 values.add(cursor.getValue());
157 }
158 for (int i = 0; i < keys.size(); i++) {
159 this.put(keys.get(i), values.get(i));
160 }
161 } else {
162 while (cursor.move()) {
163 this.put(cursor.getKey(), cursor.getValue());
164 }
165 }
166 }
167
168 @Override
169 public long getSize() {
170 return current.size();
171 }
172
173 @Override
174 public DiffCursor<K, V> getDiffCursor(Version state) {
175 MapDelta<K, V>[] backward = this.uncommittedStore.extractDeltas();
176 List<MapDelta<K, V>[]> backwardTransactions = new ArrayList<>();
177 List<MapDelta<K, V>[]> forwardTransactions = new ArrayList<>();
178
179 if (backward != null) {
180 backwardTransactions.add(backward);
181 }
182
183 if (this.previous != null) {
184 store.getPath(this.previous, state, backwardTransactions, forwardTransactions);
185 } else {
186 store.getPath(state, forwardTransactions);
187 }
188
189 return new DeltaDiffCursor<>(backwardTransactions, forwardTransactions);
190 }
191
192 @Override
193 public int contentHashCode(ContentHashCode mode) {
194 return this.current.hashCode();
195 }
196
197 @Override
198 public boolean contentEquals(AnyVersionedMap other) {
199 if (other instanceof VersionedMapDeltaImpl<?, ?> versioned) {
200 if (versioned == this) {
201 return true;
202 } else {
203 return Objects.equals(this.defaultValue, versioned.defaultValue) && Objects.equals(this.current, versioned.current);
204 }
205 } else {
206 throw new UnsupportedOperationException("Comparing different map implementations is ineffective.");
207 }
208 }
209
210 @Override
211 public void checkIntegrity() {
212 this.uncommittedStore.checkIntegrity();
213
214 for (var entry : this.current.entrySet()) {
215 var value = entry.getValue();
216 if (value == this.defaultValue) {
217 throw new IllegalStateException("Default value stored in map!");
218 } else if (value == null) {
219 throw new IllegalStateException("null value stored in map!");
220 }
221 }
222 MapTransaction<K,V> transaction = this.previous;
223 while(transaction != null) {
224 MapTransaction<K,V> parent = transaction.parent();
225 if(parent != null) {
226 if(parent.depth() != transaction.depth()-1) {
227 throw new IllegalStateException("Parent depths are inconsistent!");
228 }
229 } else {
230 if(transaction.depth() != 0) {
231 throw new IllegalArgumentException("Root depth is not 0!");
232 }
233 }
234 transaction = transaction.parent();
235 }
236 }
237}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapStoreDeltaImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapStoreDeltaImpl.java
new file mode 100644
index 00000000..ed169409
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapStoreDeltaImpl.java
@@ -0,0 +1,94 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.delta;
7
8import tools.refinery.store.map.DiffCursor;
9import tools.refinery.store.map.Version;
10import tools.refinery.store.map.VersionedMap;
11import tools.refinery.store.map.VersionedMapStore;
12
13import java.util.*;
14
15public class VersionedMapStoreDeltaImpl<K, V> implements VersionedMapStore<K, V> {
16 // Configuration
17 protected final boolean summarizeChanges;
18
19 // Static data
20 protected final V defaultValue;
21
22 public VersionedMapStoreDeltaImpl(boolean summarizeChanges, V defaultValue) {
23 this.summarizeChanges = summarizeChanges;
24 this.defaultValue = defaultValue;
25 }
26
27 @Override
28 public VersionedMap<K, V> createMap() {
29 return new VersionedMapDeltaImpl<>(this, this.summarizeChanges, this.defaultValue);
30 }
31
32 @Override
33 public VersionedMap<K, V> createMap(Version state) {
34 VersionedMapDeltaImpl<K, V> result = new VersionedMapDeltaImpl<>(this, this.summarizeChanges, this.defaultValue);
35 result.restore(state);
36 return result;
37 }
38
39 public MapTransaction<K, V> appendTransaction(MapDelta<K, V>[] deltas, MapTransaction<K, V> previous) {
40 if (deltas == null) {
41 return previous;
42 } else {
43 final int depth;
44 if(previous != null) {
45 depth = previous.depth()+1;
46 } else {
47 depth = 0;
48 }
49 return new MapTransaction<>(deltas, previous, depth);
50 }
51 }
52
53 @SuppressWarnings("unchecked")
54 private MapTransaction<K, V> getState(Version state) {
55 return (MapTransaction<K, V>) state;
56 }
57
58 public MapTransaction<K, V> getPath(Version to, List<MapDelta<K, V>[]> forwardTransactions) {
59 final MapTransaction<K, V> target = getState(to);
60 MapTransaction<K, V> toTransaction = target;
61 while (toTransaction != null) {
62 forwardTransactions.add(toTransaction.deltas());
63 toTransaction = toTransaction.parent();
64 }
65 return target;
66 }
67
68 public MapTransaction<K, V> getPath(Version from, Version to,
69 List<MapDelta<K, V>[]> backwardTransactions,
70 List<MapDelta<K, V>[]> forwardTransactions) {
71 MapTransaction<K, V> fromTransaction = getState(from);
72 final MapTransaction<K, V> target = getState(to);
73 MapTransaction<K, V> toTransaction = target;
74
75 while (fromTransaction != toTransaction) {
76 if (fromTransaction == null || (toTransaction != null && fromTransaction.depth() < toTransaction.depth())) {
77 forwardTransactions.add(toTransaction.deltas());
78 toTransaction = toTransaction.parent();
79 } else {
80 backwardTransactions.add(fromTransaction.deltas());
81 fromTransaction = fromTransaction.parent();
82 }
83 }
84 return target;
85 }
86
87 @Override
88 public DiffCursor<K, V> getDiffCursor(Version fromState, Version toState) {
89 List<MapDelta<K, V>[]> backwardTransactions = new ArrayList<>();
90 List<MapDelta<K, V>[]> forwardTransactions = new ArrayList<>();
91 getPath(fromState, toState, backwardTransactions, forwardTransactions);
92 return new DeltaDiffCursor<>(backwardTransactions, forwardTransactions);
93 }
94}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/ImmutableNode.java
index 03dffc15..5b1d8b77 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/ImmutableNode.java
@@ -1,16 +1,17 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.store.map.internal; 6package tools.refinery.store.map.internal.state;
7 7
8import java.util.Arrays; 8import java.util.Arrays;
9import java.util.Map; 9import java.util.Map;
10 10
11import tools.refinery.store.map.ContinousHashProvider; 11import tools.refinery.store.map.ContinuousHashProvider;
12import tools.refinery.store.map.Version;
12 13
13public class ImmutableNode<K, V> extends Node<K, V> { 14public class ImmutableNode<K, V> extends Node<K, V> implements Version {
14 /** 15 /**
15 * Bitmap defining the stored key and values. 16 * Bitmap defining the stored key and values.
16 */ 17 */
@@ -20,7 +21,7 @@ public class ImmutableNode<K, V> extends Node<K, V> {
20 */ 21 */
21 final int nodeMap; 22 final int nodeMap;
22 /** 23 /**
23 * Stores Keys, Values, and subnodes. Structure: (K,V)*,NODE; NODES are stored 24 * Stores Keys, Values, and sub-nodes. Structure: (K,V)*,NODE; NODES are stored
24 * backwards. 25 * backwards.
25 */ 26 */
26 final Object[] content; 27 final Object[] content;
@@ -47,8 +48,7 @@ public class ImmutableNode<K, V> extends Node<K, V> {
47 * available. 48 * available.
48 * @return an immutable version of the input node. 49 * @return an immutable version of the input node.
49 */ 50 */
50 static <K, V> ImmutableNode<K, V> constructImmutable(MutableNode<K, V> node, 51 static <K, V> ImmutableNode<K, V> constructImmutable(MutableNode<K, V> node, Map<Node<K, V>, ImmutableNode<K, V>> cache) {
51 Map<Node<K, V>, ImmutableNode<K, V>> cache) {
52 // 1. try to return from cache 52 // 1. try to return from cache
53 if (cache != null) { 53 if (cache != null) {
54 ImmutableNode<K, V> cachedResult = cache.get(node); 54 ImmutableNode<K, V> cachedResult = cache.get(node);
@@ -71,25 +71,24 @@ public class ImmutableNode<K, V> extends Node<K, V> {
71 int resultDataMap = 0; 71 int resultDataMap = 0;
72 int resultNodeMap = 0; 72 int resultNodeMap = 0;
73 final Object[] resultContent = new Object[size]; 73 final Object[] resultContent = new Object[size];
74 int bitposition = 1; 74 int bitPosition = 1;
75 for (int i = 0; i < FACTOR; i++) { 75 for (int i = 0; i < FACTOR; i++) {
76 Object key = node.content[i * 2]; 76 Object key = node.content[i * 2];
77 if (key != null) { 77 if (key != null) {
78 resultDataMap |= bitposition; 78 resultDataMap |= bitPosition;
79 resultContent[datas * 2] = key; 79 resultContent[datas * 2] = key;
80 resultContent[datas * 2 + 1] = node.content[i * 2 + 1]; 80 resultContent[datas * 2 + 1] = node.content[i * 2 + 1];
81 datas++; 81 datas++;
82 } else { 82 } else {
83 @SuppressWarnings("unchecked") 83 @SuppressWarnings("unchecked") var subnode = (Node<K, V>) node.content[i * 2 + 1];
84 var subnode = (Node<K, V>) node.content[i * 2 + 1];
85 if (subnode != null) { 84 if (subnode != null) {
86 ImmutableNode<K, V> immutableSubnode = subnode.toImmutable(cache); 85 ImmutableNode<K, V> immutableSubNode = subnode.toImmutable(cache);
87 resultNodeMap |= bitposition; 86 resultNodeMap |= bitPosition;
88 resultContent[size - 1 - nodes] = immutableSubnode; 87 resultContent[size - 1 - nodes] = immutableSubNode;
89 nodes++; 88 nodes++;
90 } 89 }
91 } 90 }
92 bitposition <<= 1; 91 bitPosition <<= 1;
93 } 92 }
94 final int resultHash = node.hashCode(); 93 final int resultHash = node.hashCode();
95 var newImmutable = new ImmutableNode<K, V>(resultDataMap, resultNodeMap, resultContent, resultHash); 94 var newImmutable = new ImmutableNode<K, V>(resultDataMap, resultNodeMap, resultContent, resultHash);
@@ -106,17 +105,15 @@ public class ImmutableNode<K, V> extends Node<K, V> {
106 } 105 }
107 106
108 @Override 107 @Override
109 public V getValue(K key, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) { 108 public V getValue(K key, ContinuousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) {
110 int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); 109 int selectedHashFragment = hashFragment(hash, shiftDepth(depth));
111 int bitposition = 1 << selectedHashFragment; 110 int bitposition = 1 << selectedHashFragment;
112 // If the key is stored as a data 111 // If the key is stored as a data
113 if ((dataMap & bitposition) != 0) { 112 if ((dataMap & bitposition) != 0) {
114 int keyIndex = 2 * index(dataMap, bitposition); 113 int keyIndex = 2 * index(dataMap, bitposition);
115 @SuppressWarnings("unchecked") 114 @SuppressWarnings("unchecked") K keyCandidate = (K) content[keyIndex];
116 K keyCandidate = (K) content[keyIndex];
117 if (keyCandidate.equals(key)) { 115 if (keyCandidate.equals(key)) {
118 @SuppressWarnings("unchecked") 116 @SuppressWarnings("unchecked") V value = (V) content[keyIndex + 1];
119 V value = (V) content[keyIndex + 1];
120 return value; 117 return value;
121 } else { 118 } else {
122 return defaultValue; 119 return defaultValue;
@@ -125,9 +122,8 @@ public class ImmutableNode<K, V> extends Node<K, V> {
125 // the key is stored as a node 122 // the key is stored as a node
126 else if ((nodeMap & bitposition) != 0) { 123 else if ((nodeMap & bitposition) != 0) {
127 int keyIndex = content.length - 1 - index(nodeMap, bitposition); 124 int keyIndex = content.length - 1 - index(nodeMap, bitposition);
128 @SuppressWarnings("unchecked") 125 @SuppressWarnings("unchecked") var subNode = (ImmutableNode<K, V>) content[keyIndex];
129 var subNode = (ImmutableNode<K, V>) content[keyIndex]; 126 int newDepth = incrementDepth(depth);
130 int newDepth = depth + 1;
131 int newHash = newHash(hashProvider, key, hash, newDepth); 127 int newHash = newHash(hashProvider, key, hash, newDepth);
132 return subNode.getValue(key, hashProvider, defaultValue, newHash, newDepth); 128 return subNode.getValue(key, hashProvider, defaultValue, newHash, newDepth);
133 } 129 }
@@ -138,44 +134,41 @@ public class ImmutableNode<K, V> extends Node<K, V> {
138 } 134 }
139 135
140 @Override 136 @Override
141 public Node<K, V> putValue(K key, V value, OldValueBox<V> oldValue, ContinousHashProvider<? super K> hashProvider, 137 public Node<K, V> putValue(K key, V value, OldValueBox<V> oldValue, ContinuousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) {
142 V defaultValue, int hash, int depth) {
143 int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); 138 int selectedHashFragment = hashFragment(hash, shiftDepth(depth));
144 int bitposition = 1 << selectedHashFragment; 139 int bitPosition = 1 << selectedHashFragment;
145 if ((dataMap & bitposition) != 0) { 140 if ((dataMap & bitPosition) != 0) {
146 int keyIndex = 2 * index(dataMap, bitposition); 141 int keyIndex = 2 * index(dataMap, bitPosition);
147 @SuppressWarnings("unchecked") 142 @SuppressWarnings("unchecked") K keyCandidate = (K) content[keyIndex];
148 K keyCandidate = (K) content[keyIndex];
149 if (keyCandidate.equals(key)) { 143 if (keyCandidate.equals(key)) {
150 if (value == defaultValue) { 144 if (value == defaultValue) {
151 // delete 145 // delete
152 MutableNode<K, V> mutable = this.toMutable(); 146 MutableNode<K, V> mutable = this.toMutable();
153 return mutable.removeEntry(selectedHashFragment, oldValue); 147 return mutable.removeEntry(selectedHashFragment, oldValue);
154 } else if (value == content[keyIndex + 1]) { 148 } else if (value == content[keyIndex + 1]) {
155 // dont change 149 // don't change
156 oldValue.setOldValue(value); 150 oldValue.setOldValue(value);
157 return this; 151 return this;
158 } else { 152 } else {
159 // update existing nodeId 153 // update existing value
160 MutableNode<K, V> mutable = this.toMutable(); 154 MutableNode<K, V> mutable = this.toMutable();
161 return mutable.updateValue(value, oldValue, selectedHashFragment); 155 return mutable.updateValue(value, oldValue, selectedHashFragment);
162 } 156 }
163 } else { 157 } else {
164 if (value == defaultValue) { 158 if (value == defaultValue) {
165 // dont change 159 // don't change
166 oldValue.setOldValue(defaultValue); 160 oldValue.setOldValue(defaultValue);
167 return this; 161 return this;
168 } else { 162 } else {
169 // add new key + nodeId 163 // add new key + value
170 MutableNode<K, V> mutable = this.toMutable(); 164 MutableNode<K, V> mutable = this.toMutable();
171 return mutable.putValue(key, value, oldValue, hashProvider, defaultValue, hash, depth); 165 return mutable.putValue(key, value, oldValue, hashProvider, defaultValue, hash, depth);
172 } 166 }
173 } 167 }
174 } else if ((nodeMap & bitposition) != 0) { 168 } else if ((nodeMap & bitPosition) != 0) {
175 int keyIndex = content.length - 1 - index(nodeMap, bitposition); 169 int keyIndex = content.length - 1 - index(nodeMap, bitPosition);
176 @SuppressWarnings("unchecked") 170 @SuppressWarnings("unchecked") var subNode = (ImmutableNode<K, V>) content[keyIndex];
177 var subNode = (ImmutableNode<K, V>) content[keyIndex]; 171 int newDepth = incrementDepth(depth);
178 int newDepth = depth + 1;
179 int newHash = newHash(hashProvider, key, hash, newDepth); 172 int newHash = newHash(hashProvider, key, hash, newDepth);
180 var newsubNode = subNode.putValue(key, value, oldValue, hashProvider, defaultValue, newHash, newDepth); 173 var newsubNode = subNode.putValue(key, value, oldValue, hashProvider, defaultValue, newHash, newDepth);
181 174
@@ -184,10 +177,11 @@ public class ImmutableNode<K, V> extends Node<K, V> {
184 return this; 177 return this;
185 } else { 178 } else {
186 MutableNode<K, V> mutable = toMutable(); 179 MutableNode<K, V> mutable = toMutable();
187 return mutable.updateWithSubNode(selectedHashFragment, newsubNode, value.equals(defaultValue)); 180 return mutable.updateWithSubNode(selectedHashFragment, newsubNode,
181 (value == null && defaultValue == null) || (value != null && value.equals(defaultValue)));
188 } 182 }
189 } else { 183 } else {
190 // add new key + nodeId 184 // add new key + value
191 MutableNode<K, V> mutable = this.toMutable(); 185 MutableNode<K, V> mutable = this.toMutable();
192 return mutable.putValue(key, value, oldValue, hashProvider, defaultValue, hash, depth); 186 return mutable.putValue(key, value, oldValue, hashProvider, defaultValue, hash, depth);
193 } 187 }
@@ -197,8 +191,7 @@ public class ImmutableNode<K, V> extends Node<K, V> {
197 public long getSize() { 191 public long getSize() {
198 int result = Integer.bitCount(this.dataMap); 192 int result = Integer.bitCount(this.dataMap);
199 for (int subnodeIndex = 0; subnodeIndex < Integer.bitCount(this.nodeMap); subnodeIndex++) { 193 for (int subnodeIndex = 0; subnodeIndex < Integer.bitCount(this.nodeMap); subnodeIndex++) {
200 @SuppressWarnings("unchecked") 194 @SuppressWarnings("unchecked") var subnode = (ImmutableNode<K, V>) this.content[this.content.length - 1 - subnodeIndex];
201 var subnode = (ImmutableNode<K, V>) this.content[this.content.length - 1 - subnodeIndex];
202 result += subnode.getSize(); 195 result += subnode.getSize();
203 } 196 }
204 return result; 197 return result;
@@ -238,6 +231,9 @@ public class ImmutableNode<K, V> extends Node<K, V> {
238 231
239 // 2. look inside the subnodes 232 // 2. look inside the subnodes
240 int nodes = Integer.bitCount(this.nodeMap); 233 int nodes = Integer.bitCount(this.nodeMap);
234 if(cursor.nodeIndexStack.peek()==null) {
235 throw new IllegalStateException("Cursor moved to the next state when the state is empty.");
236 }
241 int newNodeIndex = cursor.nodeIndexStack.peek() + 1; 237 int newNodeIndex = cursor.nodeIndexStack.peek() + 1;
242 if (newNodeIndex < nodes) { 238 if (newNodeIndex < nodes) {
243 // 2.1 found next subnode, move down to the subnode 239 // 2.1 found next subnode, move down to the subnode
@@ -264,10 +260,51 @@ public class ImmutableNode<K, V> extends Node<K, V> {
264 } 260 }
265 261
266 @Override 262 @Override
267 public void prettyPrint(StringBuilder builder, int depth, int code) { 263 @SuppressWarnings("unchecked")
268 for (int i = 0; i < depth; i++) { 264 boolean moveToNextInorder(InOrderMapCursor<K, V> cursor) {
269 builder.append("\t"); 265 if(cursor.nodeIndexStack.peek()==null) {
266 throw new IllegalStateException("Cursor moved to the next state when the state is empty.");
270 } 267 }
268
269 int position = cursor.nodeIndexStack.peek();
270 for (int index = position + 1; index < FACTOR; index++) {
271 final int mask = 1<<index;
272 if((this.dataMap & mask) != 0) {
273 // data found
274 cursor.nodeIndexStack.pop();
275 cursor.nodeIndexStack.push(index);
276
277 cursor.key = (K) this.content[2 * index(dataMap, mask)];
278 cursor.value = (V) this.content[2 * index(dataMap, mask) +1];
279 return true;
280 } else if((this.nodeMap & mask) != 0) {
281 // node found
282 Node<K,V> subnode = (Node<K, V>) this.content[this.content.length - 1 - index(nodeMap, mask)];
283 cursor.nodeIndexStack.pop();
284 cursor.nodeIndexStack.push(index);
285 cursor.nodeIndexStack.push(InOrderMapCursor.INDEX_START);
286 cursor.nodeStack.push(subnode);
287
288 return subnode.moveToNextInorder(cursor);
289 }
290 }
291
292 // nothing found
293 cursor.nodeStack.pop();
294 cursor.nodeIndexStack.pop();
295 if (!cursor.nodeStack.isEmpty()) {
296 Node<K, V> supernode = cursor.nodeStack.peek();
297 return supernode.moveToNextInorder(cursor);
298 } else {
299 cursor.key = null;
300 cursor.value = null;
301 return false;
302 }
303 }
304
305 @Override
306 public void prettyPrint(StringBuilder builder, int depth, int code) {
307 builder.append("\t".repeat(Math.max(0, depth)));
271 if (code >= 0) { 308 if (code >= 0) {
272 builder.append(code); 309 builder.append(code);
273 builder.append(":"); 310 builder.append(":");
@@ -294,17 +331,16 @@ public class ImmutableNode<K, V> extends Node<K, V> {
294 int nodeMask = 1; 331 int nodeMask = 1;
295 for (int i = 0; i < FACTOR; i++) { 332 for (int i = 0; i < FACTOR; i++) {
296 if ((nodeMask & nodeMap) != 0) { 333 if ((nodeMask & nodeMap) != 0) {
297 @SuppressWarnings("unchecked") 334 @SuppressWarnings("unchecked") Node<K, V> subNode = (Node<K, V>) content[content.length - 1 - index(nodeMap, nodeMask)];
298 Node<K, V> subNode = (Node<K, V>) content[content.length - 1 - index(nodeMap, nodeMask)];
299 builder.append("\n"); 335 builder.append("\n");
300 subNode.prettyPrint(builder, depth + 1, i); 336 subNode.prettyPrint(builder, incrementDepth(depth), i);
301 } 337 }
302 nodeMask <<= 1; 338 nodeMask <<= 1;
303 } 339 }
304 } 340 }
305 341
306 @Override 342 @Override
307 public void checkIntegrity(ContinousHashProvider<? super K> hashProvider, V defaultValue, int depth) { 343 public void checkIntegrity(ContinuousHashProvider<? super K> hashProvider, V defaultValue, int depth) {
308 if (depth > 0) { 344 if (depth > 0) {
309 boolean orphaned = Integer.bitCount(dataMap) == 1 && nodeMap == 0; 345 boolean orphaned = Integer.bitCount(dataMap) == 1 && nodeMap == 0;
310 if (orphaned) { 346 if (orphaned) {
@@ -315,12 +351,11 @@ public class ImmutableNode<K, V> extends Node<K, V> {
315 351
316 // check subnodes 352 // check subnodes
317 for (int i = 0; i < Integer.bitCount(nodeMap); i++) { 353 for (int i = 0; i < Integer.bitCount(nodeMap); i++) {
318 @SuppressWarnings("unchecked") 354 @SuppressWarnings("unchecked") var subnode = (Node<K, V>) this.content[this.content.length - 1 - i];
319 var subnode = (Node<K, V>) this.content[this.content.length - 1 - i];
320 if (!(subnode instanceof ImmutableNode<?, ?>)) { 355 if (!(subnode instanceof ImmutableNode<?, ?>)) {
321 throw new IllegalStateException("Immutable node contains mutable subnodes!"); 356 throw new IllegalStateException("Immutable node contains mutable subnodes!");
322 } else { 357 } else {
323 subnode.checkIntegrity(hashProvider, defaultValue, depth + 1); 358 subnode.checkIntegrity(hashProvider, defaultValue, incrementDepth(depth));
324 } 359 }
325 } 360 }
326 } 361 }
@@ -332,13 +367,10 @@ public class ImmutableNode<K, V> extends Node<K, V> {
332 367
333 @Override 368 @Override
334 public boolean equals(Object obj) { 369 public boolean equals(Object obj) {
335 if (this == obj) 370 if (this == obj) return true;
336 return true; 371 if (obj == null) return false;
337 if (obj == null)
338 return false;
339 if (obj instanceof ImmutableNode<?, ?> other) { 372 if (obj instanceof ImmutableNode<?, ?> other) {
340 return precalculatedHash == other.precalculatedHash && dataMap == other.dataMap && nodeMap == other.nodeMap 373 return precalculatedHash == other.precalculatedHash && dataMap == other.dataMap && nodeMap == other.nodeMap && Arrays.deepEquals(content, other.content);
341 && Arrays.deepEquals(content, other.content);
342 } else if (obj instanceof MutableNode<?, ?> mutableObj) { 374 } else if (obj instanceof MutableNode<?, ?> mutableObj) {
343 return ImmutableNode.compareImmutableMutable(this, mutableObj); 375 return ImmutableNode.compareImmutableMutable(this, mutableObj);
344 } else { 376 } else {
@@ -356,19 +388,17 @@ public class ImmutableNode<K, V> extends Node<K, V> {
356 if (key != null) { 388 if (key != null) {
357 // Check whether a new Key-Value pair can fit into the immutable container 389 // Check whether a new Key-Value pair can fit into the immutable container
358 if (datas * 2 + nodes + 2 <= immutableLength) { 390 if (datas * 2 + nodes + 2 <= immutableLength) {
359 if (!immutable.content[datas * 2].equals(key) 391 if (!immutable.content[datas * 2].equals(key) || !immutable.content[datas * 2 + 1].equals(mutable.content[i * 2 + 1])) {
360 || !immutable.content[datas * 2 + 1].equals(mutable.content[i * 2 + 1])) {
361 return false; 392 return false;
362 } 393 }
363 } else 394 } else return false;
364 return false;
365 datas++; 395 datas++;
366 } else { 396 } else {
367 var mutableSubnode = (Node<?, ?>) mutable.content[i * 2 + 1]; 397 var mutableSubnode = (Node<?, ?>) mutable.content[i * 2 + 1];
368 if (mutableSubnode != null) { 398 if (mutableSubnode != null) {
369 if (datas * 2 + nodes + 1 <= immutableLength) { 399 if (datas * 2 + nodes + 1 <= immutableLength) {
370 Object immutableSubnode = immutable.content[immutableLength - 1 - nodes]; 400 Object immutableSubNode = immutable.content[immutableLength - 1 - nodes];
371 if (!mutableSubnode.equals(immutableSubnode)) { 401 if (!mutableSubnode.equals(immutableSubNode)) {
372 return false; 402 return false;
373 } 403 }
374 nodes++; 404 nodes++;
@@ -378,6 +408,7 @@ public class ImmutableNode<K, V> extends Node<K, V> {
378 } 408 }
379 } 409 }
380 } 410 }
381 return true; 411
412 return datas * 2 + nodes == immutable.content.length;
382 } 413 }
383} 414}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/InOrderMapCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/InOrderMapCursor.java
new file mode 100644
index 00000000..7cc91004
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/InOrderMapCursor.java
@@ -0,0 +1,146 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.state;
7
8import tools.refinery.store.map.AnyVersionedMap;
9import tools.refinery.store.map.ContentHashCode;
10import tools.refinery.store.map.Cursor;
11import tools.refinery.store.map.VersionedMap;
12
13import java.util.*;
14
15public class InOrderMapCursor<K, V> implements Cursor<K, V> {
16 // Constants
17 static final int INDEX_START = -1;
18
19 // Tree stack
20 ArrayDeque<Node<K, V>> nodeStack;
21 ArrayDeque<Integer> nodeIndexStack;
22
23
24 // Values
25 K key;
26 V value;
27
28 // Hash code for checking concurrent modifications
29 final VersionedMap<K, V> map;
30 final int creationHash;
31
32 public InOrderMapCursor(VersionedMapStateImpl<K, V> map) {
33 // Initializing tree stack
34 super();
35 this.nodeStack = new ArrayDeque<>();
36 this.nodeIndexStack = new ArrayDeque<>();
37 if (map.root != null) {
38 this.nodeStack.add(map.root);
39 this.nodeIndexStack.push(INDEX_START);
40 }
41
42 // Initializing cache
43 this.key = null;
44 this.value = null;
45
46 // Initializing state
47 this.map = map;
48 this.creationHash = map.contentHashCode(ContentHashCode.APPROXIMATE_FAST);
49 }
50
51 public K getKey() {
52 return key;
53 }
54
55 public V getValue() {
56 return value;
57 }
58
59 public boolean isTerminated() {
60 return this.nodeStack.isEmpty();
61 }
62
63 public boolean move() {
64 if (isDirty()) {
65 throw new ConcurrentModificationException();
66 }
67 if (!isTerminated()) {
68 var node = this.nodeStack.peek();
69 if (node == null) {
70 throw new IllegalStateException("Cursor is not terminated but the current node is missing");
71 }
72 boolean result = node.moveToNextInorder(this);
73 if (this.nodeIndexStack.size() != this.nodeStack.size()) {
74 throw new IllegalArgumentException("Node stack is corrupted by illegal moves!");
75 }
76 return result;
77 }
78 return false;
79 }
80
81 public boolean skipCurrentNode() {
82 nodeStack.pop();
83 nodeIndexStack.pop();
84 return move();
85 }
86
87 @Override
88 public boolean isDirty() {
89 return this.map.contentHashCode(ContentHashCode.APPROXIMATE_FAST) != this.creationHash;
90 }
91
92 @Override
93 public Set<AnyVersionedMap> getDependingMaps() {
94 return Set.of(this.map);
95 }
96
97 public static <K, V> boolean sameSubNode(InOrderMapCursor<K, V> cursor1, InOrderMapCursor<K, V> cursor2) {
98 Node<K, V> nodeOfCursor1 = cursor1.nodeStack.peek();
99 Node<K, V> nodeOfCursor2 = cursor2.nodeStack.peek();
100 return Objects.equals(nodeOfCursor1, nodeOfCursor2);
101 }
102
103 /**
104 * Compares the state of two cursors started on two {@link VersionedMap} of the same
105 * {@link tools.refinery.store.map.VersionedMapStore}.
106 * @param <K> Key type
107 * @param <V> Value type
108 * @param cursor1 first cursor
109 * @param cursor2 second cursor
110 * @return Positive number if cursor 1 is behind, negative number if cursor 2 is behind, and 0 if they are at the
111 * same position.
112 */
113 public static <K, V> int comparePosition(InOrderMapCursor<K, V> cursor1, InOrderMapCursor<K, V> cursor2) {
114 // If the state does not determine the order, then compare @nodeIndexStack.
115 Iterator<Integer> nodeIndexStack1 = cursor1.nodeIndexStack.descendingIterator();
116 Iterator<Integer> nodeIndexStack2 = cursor2.nodeIndexStack.descendingIterator();
117
118 while(nodeIndexStack1.hasNext() && nodeIndexStack2.hasNext()){
119 final int index1 = nodeIndexStack1.next();
120 final int index2 = nodeIndexStack2.next();
121 if(index1 < index2) {
122 return 1;
123 } else if(index1 > index2) {
124 return -1;
125 }
126 }
127
128 return 0;
129 }
130
131 /**
132 * Compares the depth of two cursors started on @{@link VersionedMap} of the same
133 * {@link tools.refinery.store.map.VersionedMapStore}.
134 * @param <K> Key type
135 * @param <V> Value type
136 * @param cursor1 first cursor
137 * @param cursor2 second cursor
138 * @return Positive number if cursor 1 is deeper, negative number if cursor 2 is deeper, and 0 if they are at the
139 * same depth.
140 */
141 public static <K, V> int compareDepth(InOrderMapCursor<K, V> cursor1, InOrderMapCursor<K, V> cursor2) {
142 int d1 = cursor1.nodeIndexStack.size();
143 int d2 = cursor2.nodeIndexStack.size();
144 return Integer.compare(d1, d2);
145 }
146}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MapCursor.java
index f34ec7bb..0db50d04 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MapCursor.java
@@ -1,9 +1,9 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.store.map.internal; 6package tools.refinery.store.map.internal.state;
7 7
8import tools.refinery.store.map.AnyVersionedMap; 8import tools.refinery.store.map.AnyVersionedMap;
9import tools.refinery.store.map.ContentHashCode; 9import tools.refinery.store.map.ContentHashCode;
@@ -12,7 +12,6 @@ import tools.refinery.store.map.VersionedMap;
12 12
13import java.util.ArrayDeque; 13import java.util.ArrayDeque;
14import java.util.ConcurrentModificationException; 14import java.util.ConcurrentModificationException;
15import java.util.Iterator;
16import java.util.Set; 15import java.util.Set;
17 16
18public class MapCursor<K, V> implements Cursor<K, V> { 17public class MapCursor<K, V> implements Cursor<K, V> {
@@ -84,13 +83,6 @@ public class MapCursor<K, V> implements Cursor<K, V> {
84 return false; 83 return false;
85 } 84 }
86 85
87 public boolean skipCurrentNode() {
88 nodeStack.pop();
89 nodeIndexStack.pop();
90 dataIndex = INDEX_FINISH;
91 return move();
92 }
93
94 @Override 86 @Override
95 public boolean isDirty() { 87 public boolean isDirty() {
96 return this.map.contentHashCode(ContentHashCode.APPROXIMATE_FAST) != this.creationHash; 88 return this.map.contentHashCode(ContentHashCode.APPROXIMATE_FAST) != this.creationHash;
@@ -100,46 +92,4 @@ public class MapCursor<K, V> implements Cursor<K, V> {
100 public Set<AnyVersionedMap> getDependingMaps() { 92 public Set<AnyVersionedMap> getDependingMaps() {
101 return Set.of(this.map); 93 return Set.of(this.map);
102 } 94 }
103
104 public static <K, V> boolean sameSubnode(MapCursor<K, V> cursor1, MapCursor<K, V> cursor2) {
105 Node<K, V> nodeOfCursor1 = cursor1.nodeStack.peek();
106 Node<K, V> nodeOfCursor2 = cursor2.nodeStack.peek();
107 if (nodeOfCursor1 != null && nodeOfCursor2 != null) {
108 return nodeOfCursor1.equals(nodeOfCursor2);
109 } else {
110 return false;
111 }
112 }
113
114 /**
115 * @param <K>
116 * @param <V>
117 * @param cursor1
118 * @param cursor2
119 * @return Positive number if cursor 1 is behind, negative number if cursor 2 is behind, and 0 if they are at the
120 * same position.
121 */
122 public static <K, V> int compare(MapCursor<K, V> cursor1, MapCursor<K, V> cursor2) {
123 // two cursors are equally deep
124 Iterator<Integer> stack1 = cursor1.nodeIndexStack.descendingIterator();
125 Iterator<Integer> stack2 = cursor2.nodeIndexStack.descendingIterator();
126 if (stack1.hasNext()) {
127 if (!stack2.hasNext()) {
128 // stack 2 has no more element, thus stack 1 is deeper
129 return 1;
130 }
131 int val1 = stack1.next();
132 int val2 = stack2.next();
133 if (val1 < val2) {
134 return -1;
135 } else if (val2 < val1) {
136 return 1;
137 }
138 }
139 if (stack2.hasNext()) {
140 // stack 2 has more element, thus stack 2 is deeper
141 return 1;
142 }
143 return Integer.compare(cursor1.dataIndex, cursor2.dataIndex);
144 }
145} 95}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MapDiffCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MapDiffCursor.java
new file mode 100644
index 00000000..083bf8cf
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MapDiffCursor.java
@@ -0,0 +1,264 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.state;
7
8import tools.refinery.store.map.AnyVersionedMap;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.map.DiffCursor;
11
12import java.util.Objects;
13import java.util.Set;
14import java.util.stream.Collectors;
15import java.util.stream.Stream;
16
17/**
18 * A cursor representing the difference between two states of a map.
19 *
20 * @author Oszkar Semerath
21 */
22public class MapDiffCursor<K, V> implements DiffCursor<K, V>, Cursor<K, V> {
23 private enum State {
24 /**
25 * initialized state.
26 */
27 INIT,
28 /**
29 * Unstable state.
30 */
31 MOVING_MOVING_SAME_KEY_SAME_VALUE,
32 /**
33 * Both cursors are moving, and they are on the same sub-node.
34 */
35 MOVING_MOVING_SAME_NODE,
36 /**
37 * Both cursors are moving, cursor 1 is behind.
38 */
39 MOVING_MOVING_BEHIND1,
40 /**
41 * Both cursors are moving, cursor 2 is behind.
42 */
43 MOVING_MOVING_BEHIND2,
44 /**
45 * Both cursors are moving, cursor 1 is on the same key as cursor 2, values are different
46 */
47 MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE,
48 /**
49 * Cursor 1 is moving, Cursor 2 is terminated.
50 */
51 MOVING_TERMINATED,
52 /**
53 * Cursor 1 is terminated , Cursor 2 is moving.
54 */
55 TERMINATED_MOVING,
56 /**
57 * Both cursors are terminated.
58 */
59 TERMINATED_TERMINATED,
60 /**
61 * Both Cursors are moving, and they are on an incomparable position.
62 * It is resolved by showing Cursor 1.
63 */
64 MOVING_MOVING_HASH1,
65 /**
66 * Both Cursors are moving, and they are on an incomparable position.
67 * It is resolved by showing Cursor 2.
68 */
69 MOVING_MOVING_HASH2
70 }
71
72 /**
73 * Default nodeId representing missing elements.
74 */
75 private final V defaultValue;
76 private final InOrderMapCursor<K, V> cursor1;
77 private final InOrderMapCursor<K, V> cursor2;
78
79 // State
80 State state = State.INIT;
81
82 // Values
83 private K key;
84 private V fromValue;
85 private V toValue;
86
87
88 public MapDiffCursor(V defaultValue, InOrderMapCursor<K, V> cursor1, InOrderMapCursor<K, V> cursor2) {
89 super();
90 this.defaultValue = defaultValue;
91 this.cursor1 = cursor1;
92 this.cursor2 = cursor2;
93 }
94
95 @Override
96 public K getKey() {
97 return key;
98 }
99
100 @Override
101 public V getFromValue() {
102 return fromValue;
103 }
104
105 @Override
106 public V getToValue() {
107 return toValue;
108 }
109
110 @Override
111 public V getValue() {
112 return getToValue();
113 }
114
115 public boolean isTerminated() {
116 return this.state == State.TERMINATED_TERMINATED;
117 }
118
119 @Override
120 public boolean isDirty() {
121 return this.cursor1.isDirty() || this.cursor2.isDirty();
122 }
123
124 @Override
125 public Set<AnyVersionedMap> getDependingMaps() {
126 return Stream.concat(cursor1.getDependingMaps().stream(), cursor2.getDependingMaps().stream()).map(AnyVersionedMap.class::cast).collect(Collectors.toUnmodifiableSet());
127 }
128
129 private boolean isInStableState() {
130 return this.state != State.MOVING_MOVING_SAME_KEY_SAME_VALUE
131 && this.state != State.MOVING_MOVING_SAME_NODE && this.state != State.INIT;
132 }
133
134 private boolean updateAndReturnWithResult() {
135 return switch (this.state) {
136 case INIT -> throw new IllegalStateException("DiffCursor terminated without starting!");
137 case MOVING_MOVING_SAME_KEY_SAME_VALUE, MOVING_MOVING_SAME_NODE ->
138 throw new IllegalStateException("DiffCursor terminated in unstable state!");
139 case MOVING_MOVING_BEHIND1, MOVING_TERMINATED, MOVING_MOVING_HASH1 -> {
140 this.key = this.cursor1.getKey();
141 this.fromValue = this.cursor1.getValue();
142 this.toValue = this.defaultValue;
143 yield true;
144 }
145 case MOVING_MOVING_BEHIND2, TERMINATED_MOVING, MOVING_MOVING_HASH2 -> {
146 this.key = this.cursor2.getKey();
147 this.fromValue = this.defaultValue;
148 this.toValue = cursor2.getValue();
149 yield true;
150 }
151 case MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE -> {
152 this.key = this.cursor1.getKey();
153 this.fromValue = this.cursor1.getValue();
154 this.toValue = this.cursor2.getValue();
155 yield true;
156 }
157 case TERMINATED_TERMINATED -> {
158 this.key = null;
159 this.fromValue = null;
160 this.toValue = null;
161 yield false;
162 }
163 };
164 }
165
166 public boolean move() {
167 do {
168 this.state = moveOne(this.state);
169 } while (!isInStableState());
170 return updateAndReturnWithResult();
171 }
172
173 private State moveOne(State currentState) {
174 return switch (currentState) {
175 case INIT, MOVING_MOVING_HASH2, MOVING_MOVING_SAME_KEY_SAME_VALUE, MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE -> {
176 boolean cursor1Moved = cursor1.move();
177 boolean cursor2Moved = cursor2.move();
178 yield recalculateStateAfterCursorMovement(cursor1Moved, cursor2Moved);
179 }
180 case MOVING_MOVING_SAME_NODE -> {
181 boolean cursor1Moved = cursor1.skipCurrentNode();
182 boolean cursor2Moved = cursor2.skipCurrentNode();
183 yield recalculateStateAfterCursorMovement(cursor1Moved, cursor2Moved);
184 }
185 case MOVING_MOVING_BEHIND1 -> {
186 boolean cursorMoved = cursor1.move();
187 if (cursorMoved) {
188 yield recalculateStateBasedOnCursorRelation();
189 } else {
190 yield State.TERMINATED_MOVING;
191 }
192 }
193 case MOVING_MOVING_BEHIND2 -> {
194 boolean cursorMoved = cursor2.move();
195 if (cursorMoved) {
196 yield recalculateStateBasedOnCursorRelation();
197 } else {
198 yield State.MOVING_TERMINATED;
199 }
200 }
201 case TERMINATED_MOVING -> {
202 boolean cursorMoved = cursor2.move();
203 if (cursorMoved) {
204 yield State.TERMINATED_MOVING;
205 } else {
206 yield State.TERMINATED_TERMINATED;
207 }
208 }
209 case MOVING_TERMINATED -> {
210 boolean cursorMoved = cursor1.move();
211 if (cursorMoved) {
212 yield State.MOVING_TERMINATED;
213 } else {
214 yield State.TERMINATED_TERMINATED;
215 }
216 }
217 case MOVING_MOVING_HASH1 -> State.MOVING_MOVING_HASH2;
218 case TERMINATED_TERMINATED -> throw new IllegalStateException("Trying to move while terminated!");
219 };
220 }
221
222 private State recalculateStateAfterCursorMovement(boolean cursor1Moved, boolean cursor2Moved) {
223 if (cursor1Moved && cursor2Moved) {
224 return recalculateStateBasedOnCursorRelation();
225 } else if (cursor1Moved) {
226 return State.MOVING_TERMINATED;
227 } else if (cursor2Moved) {
228 return State.TERMINATED_MOVING;
229 } else {
230 return State.TERMINATED_TERMINATED;
231 }
232 }
233
234 private State recalculateStateBasedOnCursorRelation() {
235 final int relation = InOrderMapCursor.comparePosition(cursor1, cursor2);
236
237 if (relation > 0) {
238 return State.MOVING_MOVING_BEHIND1;
239 } else if (relation < 0) {
240 return State.MOVING_MOVING_BEHIND2;
241 }
242
243 if (InOrderMapCursor.sameSubNode(cursor1, cursor2)) {
244 return State.MOVING_MOVING_SAME_NODE;
245 } else if (Objects.equals(cursor1.getKey(), cursor2.getKey())) {
246 if (Objects.equals(cursor1.getValue(), cursor2.getValue())) {
247 return State.MOVING_MOVING_SAME_KEY_SAME_VALUE;
248 } else {
249 return State.MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE;
250 }
251 }
252
253 final int depth = InOrderMapCursor.compareDepth(cursor1, cursor2);
254
255 if (depth > 0) {
256 return State.MOVING_MOVING_BEHIND1;
257 } else if (depth < 0) {
258 return State.MOVING_MOVING_BEHIND2;
259 } else {
260 return State.MOVING_MOVING_HASH1;
261 }
262
263 }
264}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MutableNode.java
index 1129ee5a..408ed62c 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MutableNode.java
@@ -1,26 +1,26 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.store.map.internal; 6package tools.refinery.store.map.internal.state;
7 7
8import tools.refinery.store.map.ContinousHashProvider; 8import tools.refinery.store.map.ContinuousHashProvider;
9 9
10import java.util.Arrays; 10import java.util.Arrays;
11import java.util.Map; 11import java.util.Map;
12 12
13public class MutableNode<K, V> extends Node<K, V> { 13public class MutableNode<K, V> extends Node<K, V> {
14 int cachedHash; 14 int cachedHash;
15 protected boolean cachedHashValid;
15 protected Object[] content; 16 protected Object[] content;
16 17
17 protected MutableNode() { 18 protected MutableNode() {
18 this.content = new Object[2 * FACTOR]; 19 this.content = new Object[2 * FACTOR];
19 updateHash(); 20 invalidateHash();
20 } 21 }
21 22
22 public static <K, V> MutableNode<K, V> initialize(K key, V value, ContinousHashProvider<? super K> hashProvider, 23 public static <K, V> MutableNode<K, V> initialize(K key, V value, ContinuousHashProvider<? super K> hashProvider, V defaultValue) {
23 V defaultValue) {
24 if (value == defaultValue) { 24 if (value == defaultValue) {
25 return null; 25 return null;
26 } else { 26 } else {
@@ -29,7 +29,7 @@ public class MutableNode<K, V> extends Node<K, V> {
29 MutableNode<K, V> res = new MutableNode<>(); 29 MutableNode<K, V> res = new MutableNode<>();
30 res.content[2 * fragment] = key; 30 res.content[2 * fragment] = key;
31 res.content[2 * fragment + 1] = value; 31 res.content[2 * fragment + 1] = value;
32 res.updateHash(); 32 res.invalidateHash();
33 return res; 33 return res;
34 } 34 }
35 } 35 }
@@ -37,44 +37,41 @@ public class MutableNode<K, V> extends Node<K, V> {
37 /** 37 /**
38 * Constructs a {@link MutableNode} as a copy of an {@link ImmutableNode} 38 * Constructs a {@link MutableNode} as a copy of an {@link ImmutableNode}
39 * 39 *
40 * @param node 40 * @param node to be transformed
41 */ 41 */
42 protected MutableNode(ImmutableNode<K, V> node) { 42 protected MutableNode(ImmutableNode<K, V> node) {
43 this.content = new Object[2 * FACTOR]; 43 this.content = new Object[2 * FACTOR];
44 int dataUsed = 0; 44 int dataUsed = 0;
45 int nodeUsed = 0; 45 int nodeUsed = 0;
46 for (int i = 0; i < FACTOR; i++) { 46 for (int i = 0; i < FACTOR; i++) {
47 int bitposition = 1 << i; 47 int bitPosition = 1 << i;
48 if ((node.dataMap & bitposition) != 0) { 48 if ((node.dataMap & bitPosition) != 0) {
49 content[2 * i] = node.content[dataUsed * 2]; 49 content[2 * i] = node.content[dataUsed * 2];
50 content[2 * i + 1] = node.content[dataUsed * 2 + 1]; 50 content[2 * i + 1] = node.content[dataUsed * 2 + 1];
51 dataUsed++; 51 dataUsed++;
52 } else if ((node.nodeMap & bitposition) != 0) { 52 } else if ((node.nodeMap & bitPosition) != 0) {
53 content[2 * i + 1] = node.content[node.content.length - 1 - nodeUsed]; 53 content[2 * i + 1] = node.content[node.content.length - 1 - nodeUsed];
54 nodeUsed++; 54 nodeUsed++;
55 } 55 }
56 } 56 }
57 this.cachedHash = node.hashCode(); 57 this.cachedHashValid = false;
58 } 58 }
59 59
60 @Override 60 @Override
61 public V getValue(K key, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) { 61 public V getValue(K key, ContinuousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) {
62 int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); 62 int selectedHashFragment = hashFragment(hash, shiftDepth(depth));
63 @SuppressWarnings("unchecked") 63 @SuppressWarnings("unchecked") K keyCandidate = (K) this.content[2 * selectedHashFragment];
64 K keyCandidate = (K) this.content[2 * selectedHashFragment];
65 if (keyCandidate != null) { 64 if (keyCandidate != null) {
66 if (keyCandidate.equals(key)) { 65 if (keyCandidate.equals(key)) {
67 @SuppressWarnings("unchecked") 66 @SuppressWarnings("unchecked") V value = (V) this.content[2 * selectedHashFragment + 1];
68 V value = (V) this.content[2 * selectedHashFragment + 1];
69 return value; 67 return value;
70 } else { 68 } else {
71 return defaultValue; 69 return defaultValue;
72 } 70 }
73 } else { 71 } else {
74 @SuppressWarnings("unchecked") 72 @SuppressWarnings("unchecked") var nodeCandidate = (Node<K, V>) content[2 * selectedHashFragment + 1];
75 var nodeCandidate = (Node<K, V>) content[2 * selectedHashFragment + 1];
76 if (nodeCandidate != null) { 73 if (nodeCandidate != null) {
77 int newDepth = depth + 1; 74 int newDepth = incrementDepth(depth);
78 int newHash = newHash(hashProvider, key, hash, newDepth); 75 int newHash = newHash(hashProvider, key, hash, newDepth);
79 return nodeCandidate.getValue(key, hashProvider, defaultValue, newHash, newDepth); 76 return nodeCandidate.getValue(key, hashProvider, defaultValue, newHash, newDepth);
80 } else { 77 } else {
@@ -84,13 +81,11 @@ public class MutableNode<K, V> extends Node<K, V> {
84 } 81 }
85 82
86 @Override 83 @Override
87 public Node<K, V> putValue(K key, V value, OldValueBox<V> oldValueBox, ContinousHashProvider<? super K> hashProvider, 84 public Node<K, V> putValue(K key, V value, OldValueBox<V> oldValueBox, ContinuousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) {
88 V defaultValue, int hash, int depth) {
89 int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); 85 int selectedHashFragment = hashFragment(hash, shiftDepth(depth));
90 @SuppressWarnings("unchecked") 86 @SuppressWarnings("unchecked") K keyCandidate = (K) content[2 * selectedHashFragment];
91 K keyCandidate = (K) content[2 * selectedHashFragment];
92 if (keyCandidate != null) { 87 if (keyCandidate != null) {
93 // If has key 88 // If it has key
94 if (keyCandidate.equals(key)) { 89 if (keyCandidate.equals(key)) {
95 // The key is equals to an existing key -> update entry 90 // The key is equals to an existing key -> update entry
96 if (value == defaultValue) { 91 if (value == defaultValue) {
@@ -111,25 +106,22 @@ public class MutableNode<K, V> extends Node<K, V> {
111 return moveDownAndSplit(hashProvider, key, value, keyCandidate, hash, depth, selectedHashFragment); 106 return moveDownAndSplit(hashProvider, key, value, keyCandidate, hash, depth, selectedHashFragment);
112 } 107 }
113 } 108 }
109 }
110 // If it does not have key, check for value
111 @SuppressWarnings("unchecked") var nodeCandidate = (Node<K, V>) content[2 * selectedHashFragment + 1];
112 if (nodeCandidate != null) {
113 // If it has value, it is a sub-node -> update that
114 int newDepth = incrementDepth(depth);
115 var newNode = nodeCandidate.putValue(key, value, oldValueBox, hashProvider, defaultValue, newHash(hashProvider, key, hash, newDepth), newDepth);
116 return updateWithSubNode(selectedHashFragment, newNode, (value == null && defaultValue == null) || (value != null && value.equals(defaultValue)));
114 } else { 117 } else {
115 // If it does not have key, check for nodeId 118 // If it does not have value, put it in the empty place
116 @SuppressWarnings("unchecked") 119 if (value == defaultValue) {
117 var nodeCandidate = (Node<K, V>) content[2 * selectedHashFragment + 1]; 120 // don't need to add new key-value pair
118 if (nodeCandidate != null) { 121 oldValueBox.setOldValue(defaultValue);
119 // If it has nodeId, it is a subnode -> upate that 122 return this;
120 var newNode = nodeCandidate.putValue(key, value, oldValueBox, hashProvider, defaultValue,
121 newHash(hashProvider, key, hash, depth + 1), depth + 1);
122 return updateWithSubNode(selectedHashFragment, newNode, value.equals(defaultValue));
123 } else { 123 } else {
124 // If it does not have nodeId, put it in the empty place 124 return addEntry(key, value, oldValueBox, selectedHashFragment, defaultValue);
125 if (value == defaultValue) {
126 // dont need to add new key-nodeId pair
127 oldValueBox.setOldValue(defaultValue);
128 return this;
129 } else {
130 return addEntry(key, value, oldValueBox, selectedHashFragment, defaultValue);
131 }
132
133 } 125 }
134 } 126 }
135 } 127 }
@@ -138,30 +130,31 @@ public class MutableNode<K, V> extends Node<K, V> {
138 content[2 * selectedHashFragment] = key; 130 content[2 * selectedHashFragment] = key;
139 oldValueBox.setOldValue(defaultValue); 131 oldValueBox.setOldValue(defaultValue);
140 content[2 * selectedHashFragment + 1] = value; 132 content[2 * selectedHashFragment + 1] = value;
141 updateHash(); 133 invalidateHash();
142 return this; 134 return this;
143 } 135 }
144 136
145 /** 137 /**
146 * Updates an entry in a selected hash-fragment to a non-default nodeId. 138 * Updates an entry in a selected hash-fragment to a non-default value.
147 * 139 *
148 * @param value 140 * @param value new value
149 * @param selectedHashFragment 141 * @param selectedHashFragment position of the value
150 * @return 142 * @return updated node
151 */ 143 */
152 @SuppressWarnings("unchecked") 144 @SuppressWarnings("unchecked")
153 Node<K, V> updateValue(V value, OldValueBox<V> oldValue, int selectedHashFragment) { 145 Node<K, V> updateValue(V value, OldValueBox<V> oldValue, int selectedHashFragment) {
154 oldValue.setOldValue((V) content[2 * selectedHashFragment + 1]); 146 oldValue.setOldValue((V) content[2 * selectedHashFragment + 1]);
155 content[2 * selectedHashFragment + 1] = value; 147 content[2 * selectedHashFragment + 1] = value;
156 updateHash(); 148 invalidateHash();
157 return this; 149 return this;
158 } 150 }
159 151
160 /** 152 /**
153 * Updates an entry in a selected hash-fragment with a subtree.
161 * 154 *
162 * @param selectedHashFragment 155 * @param selectedHashFragment position of the value
163 * @param newNode 156 * @param newNode the subtree
164 * @return 157 * @return updated node
165 */ 158 */
166 Node<K, V> updateWithSubNode(int selectedHashFragment, Node<K, V> newNode, boolean deletionHappened) { 159 Node<K, V> updateWithSubNode(int selectedHashFragment, Node<K, V> newNode, boolean deletionHappened) {
167 if (deletionHappened) { 160 if (deletionHappened) {
@@ -169,7 +162,7 @@ public class MutableNode<K, V> extends Node<K, V> {
169 // Check whether this node become empty 162 // Check whether this node become empty
170 content[2 * selectedHashFragment + 1] = null; // i.e. the new node 163 content[2 * selectedHashFragment + 1] = null; // i.e. the new node
171 if (hasContent()) { 164 if (hasContent()) {
172 updateHash(); 165 invalidateHash();
173 return this; 166 return this;
174 } else { 167 } else {
175 return null; 168 return null;
@@ -180,10 +173,10 @@ public class MutableNode<K, V> extends Node<K, V> {
180 if (immutableNewNode != null) { 173 if (immutableNewNode != null) {
181 int orphaned = immutableNewNode.isOrphaned(); 174 int orphaned = immutableNewNode.isOrphaned();
182 if (orphaned >= 0) { 175 if (orphaned >= 0) {
183 // orphan subnode data is replaced with data 176 // orphan sub-node data is replaced with data
184 content[2 * selectedHashFragment] = immutableNewNode.content[orphaned * 2]; 177 content[2 * selectedHashFragment] = immutableNewNode.content[orphaned * 2];
185 content[2 * selectedHashFragment + 1] = immutableNewNode.content[orphaned * 2 + 1]; 178 content[2 * selectedHashFragment + 1] = immutableNewNode.content[orphaned * 2 + 1];
186 updateHash(); 179 invalidateHash();
187 return this; 180 return this;
188 } 181 }
189 } 182 }
@@ -191,15 +184,13 @@ public class MutableNode<K, V> extends Node<K, V> {
191 } 184 }
192 // normal behaviour 185 // normal behaviour
193 content[2 * selectedHashFragment + 1] = newNode; 186 content[2 * selectedHashFragment + 1] = newNode;
194 updateHash(); 187 invalidateHash();
195 return this; 188 return this;
196
197 } 189 }
198 190
199 private boolean hasContent() { 191 private boolean hasContent() {
200 for (Object element : this.content) { 192 for (Object element : this.content) {
201 if (element != null) 193 if (element != null) return true;
202 return true;
203 } 194 }
204 return false; 195 return false;
205 } 196 }
@@ -226,27 +217,24 @@ public class MutableNode<K, V> extends Node<K, V> {
226 } 217 }
227 218
228 @SuppressWarnings("unchecked") 219 @SuppressWarnings("unchecked")
229 private Node<K, V> moveDownAndSplit(ContinousHashProvider<? super K> hashProvider, K newKey, V newValue, 220 private Node<K, V> moveDownAndSplit(ContinuousHashProvider<? super K> hashProvider, K newKey, V newValue, K previousKey, int hashOfNewKey, int depth, int selectedHashFragmentOfCurrentDepth) {
230 K previousKey, int hashOfNewKey, int depth, int selectedHashFragmentOfCurrentDepth) {
231 V previousValue = (V) content[2 * selectedHashFragmentOfCurrentDepth + 1]; 221 V previousValue = (V) content[2 * selectedHashFragmentOfCurrentDepth + 1];
232 222
233 MutableNode<K, V> newSubNode = newNodeWithTwoEntries(hashProvider, previousKey, previousValue, 223 MutableNode<K, V> newSubNode = newNodeWithTwoEntries(hashProvider, previousKey, previousValue, hashProvider.getHash(previousKey, hashDepth(depth)), newKey, newValue, hashOfNewKey, incrementDepth(depth));
234 hashProvider.getHash(previousKey, hashDepth(depth)), newKey, newValue, hashOfNewKey, depth + 1);
235 224
236 content[2 * selectedHashFragmentOfCurrentDepth] = null; 225 content[2 * selectedHashFragmentOfCurrentDepth] = null;
237 content[2 * selectedHashFragmentOfCurrentDepth + 1] = newSubNode; 226 content[2 * selectedHashFragmentOfCurrentDepth + 1] = newSubNode;
238 updateHash(); 227 invalidateHash();
239 return this; 228 return this;
240 } 229 }
241 230
242 // Pass everything as parameters for performance. 231 // Pass everything as parameters for performance.
243 @SuppressWarnings("squid:S107") 232 @SuppressWarnings("squid:S107")
244 private MutableNode<K, V> newNodeWithTwoEntries(ContinousHashProvider<? super K> hashProvider, K key1, V value1, 233 private MutableNode<K, V> newNodeWithTwoEntries(ContinuousHashProvider<? super K> hashProvider, K key1, V value1, int oldHash1, K key2, V value2, int oldHash2, int newDepth) {
245 int oldHash1, K key2, V value2, int oldHash2, int newdepth) { 234 int newHash1 = newHash(hashProvider, key1, oldHash1, newDepth);
246 int newHash1 = newHash(hashProvider, key1, oldHash1, newdepth); 235 int newHash2 = newHash(hashProvider, key2, oldHash2, newDepth);
247 int newHash2 = newHash(hashProvider, key2, oldHash2, newdepth); 236 int newFragment1 = hashFragment(newHash1, shiftDepth(newDepth));
248 int newFragment1 = hashFragment(newHash1, shiftDepth(newdepth)); 237 int newFragment2 = hashFragment(newHash2, shiftDepth(newDepth));
249 int newFragment2 = hashFragment(newHash2, shiftDepth(newdepth));
250 238
251 MutableNode<K, V> subNode = new MutableNode<>(); 239 MutableNode<K, V> subNode = new MutableNode<>();
252 if (newFragment1 != newFragment2) { 240 if (newFragment1 != newFragment2) {
@@ -256,11 +244,10 @@ public class MutableNode<K, V> extends Node<K, V> {
256 subNode.content[newFragment2 * 2] = key2; 244 subNode.content[newFragment2 * 2] = key2;
257 subNode.content[newFragment2 * 2 + 1] = value2; 245 subNode.content[newFragment2 * 2 + 1] = value2;
258 } else { 246 } else {
259 MutableNode<K, V> subSubNode = newNodeWithTwoEntries(hashProvider, key1, value1, newHash1, key2, value2, 247 MutableNode<K, V> subSubNode = newNodeWithTwoEntries(hashProvider, key1, value1, newHash1, key2, value2, newHash2, incrementDepth(newDepth));
260 newHash2, newdepth + 1);
261 subNode.content[newFragment1 * 2 + 1] = subSubNode; 248 subNode.content[newFragment1 * 2 + 1] = subSubNode;
262 } 249 }
263 subNode.updateHash(); 250 subNode.invalidateHash();
264 return subNode; 251 return subNode;
265 } 252 }
266 253
@@ -270,7 +257,7 @@ public class MutableNode<K, V> extends Node<K, V> {
270 oldValue.setOldValue((V) content[2 * selectedHashFragment + 1]); 257 oldValue.setOldValue((V) content[2 * selectedHashFragment + 1]);
271 content[2 * selectedHashFragment + 1] = null; 258 content[2 * selectedHashFragment + 1] = null;
272 if (hasContent()) { 259 if (hasContent()) {
273 updateHash(); 260 invalidateHash();
274 return this; 261 return this;
275 } else { 262 } else {
276 return null; 263 return null;
@@ -321,10 +308,13 @@ public class MutableNode<K, V> extends Node<K, V> {
321 cursor.dataIndex = MapCursor.INDEX_FINISH; 308 cursor.dataIndex = MapCursor.INDEX_FINISH;
322 } 309 }
323 310
324 // 2. look inside the subnodes 311 // 2. look inside the sub-nodes
312 if(cursor.nodeIndexStack.peek()==null) {
313 throw new IllegalStateException("Cursor moved to the next state when the state is empty.");
314 }
325 for (int index = cursor.nodeIndexStack.peek() + 1; index < FACTOR; index++) { 315 for (int index = cursor.nodeIndexStack.peek() + 1; index < FACTOR; index++) {
326 if (this.content[index * 2] == null && this.content[index * 2 + 1] != null) { 316 if (this.content[index * 2] == null && this.content[index * 2 + 1] != null) {
327 // 2.1 found next subnode, move down to the subnode 317 // 2.1 found next sub-node, move down to the sub-node
328 Node<K, V> subnode = (Node<K, V>) this.content[index * 2 + 1]; 318 Node<K, V> subnode = (Node<K, V>) this.content[index * 2 + 1];
329 319
330 cursor.dataIndex = MapCursor.INDEX_START; 320 cursor.dataIndex = MapCursor.INDEX_START;
@@ -336,7 +326,7 @@ public class MutableNode<K, V> extends Node<K, V> {
336 return subnode.moveToNext(cursor); 326 return subnode.moveToNext(cursor);
337 } 327 }
338 } 328 }
339 // 3. no subnode found, move up 329 // 3. no sub-node found, move up
340 cursor.nodeStack.pop(); 330 cursor.nodeStack.pop();
341 cursor.nodeIndexStack.pop(); 331 cursor.nodeIndexStack.pop();
342 if (!cursor.nodeStack.isEmpty()) { 332 if (!cursor.nodeStack.isEmpty()) {
@@ -350,10 +340,51 @@ public class MutableNode<K, V> extends Node<K, V> {
350 } 340 }
351 341
352 @Override 342 @Override
353 public void prettyPrint(StringBuilder builder, int depth, int code) { 343 @SuppressWarnings("unchecked")
354 for (int i = 0; i < depth; i++) { 344 boolean moveToNextInorder(InOrderMapCursor<K,V> cursor) {
355 builder.append("\t"); 345 if(cursor.nodeIndexStack.peek()==null || cursor.nodeStack.peek()==null) {
346 throw new IllegalStateException("Cursor moved to the next state when the state is empty.");
356 } 347 }
348
349 int position = cursor.nodeIndexStack.peek();
350
351 for (int index = position + 1; index < FACTOR; index++) {
352 // data found
353 if (this.content[index * 2] != null) {
354 cursor.nodeIndexStack.pop();
355 cursor.nodeIndexStack.push(index);
356
357 cursor.key = (K) this.content[index * 2];
358 cursor.value = (V) this.content[index * 2 + 1];
359 return true;
360 } else if (this.content[index * 2 +1] != null) {
361 // sub-node found
362 Node<K,V> subnode = (Node<K, V>) this.content[index * 2 +1];
363 cursor.nodeIndexStack.pop();
364 cursor.nodeIndexStack.push(index);
365 cursor.nodeIndexStack.push(InOrderMapCursor.INDEX_START);
366 cursor.nodeStack.push(subnode);
367
368 return subnode.moveToNextInorder(cursor);
369 }
370 }
371
372 // nothing found
373 cursor.nodeStack.pop();
374 cursor.nodeIndexStack.pop();
375 if (!cursor.nodeStack.isEmpty()) {
376 Node<K, V> supernode = cursor.nodeStack.peek();
377 return supernode.moveToNextInorder(cursor);
378 } else {
379 cursor.key = null;
380 cursor.value = null;
381 return false;
382 }
383 }
384
385 @Override
386 public void prettyPrint(StringBuilder builder, int depth, int code) {
387 builder.append("\t".repeat(Math.max(0, depth)));
357 if (code >= 0) { 388 if (code >= 0) {
358 builder.append(code); 389 builder.append(code);
359 builder.append(":"); 390 builder.append(":");
@@ -376,19 +407,18 @@ public class MutableNode<K, V> extends Node<K, V> {
376 } 407 }
377 } 408 }
378 builder.append(")"); 409 builder.append(")");
379 // print subnodes 410 // print sub-nodes
380 for (int i = 0; i < FACTOR; i++) { 411 for (int i = 0; i < FACTOR; i++) {
381 if (content[2 * i] == null && content[2 * i + 1] != null) { 412 if (content[2 * i] == null && content[2 * i + 1] != null) {
382 @SuppressWarnings("unchecked") 413 @SuppressWarnings("unchecked") Node<K, V> subNode = (Node<K, V>) content[2 * i + 1];
383 Node<K, V> subNode = (Node<K, V>) content[2 * i + 1];
384 builder.append("\n"); 414 builder.append("\n");
385 subNode.prettyPrint(builder, depth + 1, i); 415 subNode.prettyPrint(builder, incrementDepth(depth), i);
386 } 416 }
387 } 417 }
388 } 418 }
389 419
390 @Override 420 @Override
391 public void checkIntegrity(ContinousHashProvider<? super K> hashProvider, V defaultValue, int depth) { 421 public void checkIntegrity(ContinuousHashProvider<? super K> hashProvider, V defaultValue, int depth) {
392 // check for orphan nodes 422 // check for orphan nodes
393 if (depth > 0) { 423 if (depth > 0) {
394 int orphaned = isOrphaned(); 424 int orphaned = isOrphaned();
@@ -399,57 +429,67 @@ public class MutableNode<K, V> extends Node<K, V> {
399 // check the place of data 429 // check the place of data
400 for (int i = 0; i < FACTOR; i++) { 430 for (int i = 0; i < FACTOR; i++) {
401 if (this.content[2 * i] != null) { 431 if (this.content[2 * i] != null) {
402 @SuppressWarnings("unchecked") 432 @SuppressWarnings("unchecked") K key = (K) this.content[2 * i];
403 K key = (K) this.content[2 * i]; 433 @SuppressWarnings("unchecked") V value = (V) this.content[2 * i + 1];
404 @SuppressWarnings("unchecked")
405 V value = (V) this.content[2 * i + 1];
406 434
407 if (value == defaultValue) { 435 if (value == defaultValue) {
408 throw new IllegalStateException("Node contains default nodeId!"); 436 throw new IllegalStateException("Node contains default value!");
409 } 437 }
410 int hashCode = hashProvider.getHash(key, hashDepth(depth)); 438 int hashCode = hashProvider.getHash(key, hashDepth(depth));
411 int shiftDepth = shiftDepth(depth); 439 int shiftDepth = shiftDepth(depth);
412 int selectedHashFragment = hashFragment(hashCode, shiftDepth); 440 int selectedHashFragment = hashFragment(hashCode, shiftDepth);
413 if (i != selectedHashFragment) { 441 if (i != selectedHashFragment) {
414 throw new IllegalStateException("Key " + key + " with hash code " + hashCode 442 throw new IllegalStateException("Key " + key + " with hash code " + hashCode + " is in bad place! Fragment=" + selectedHashFragment + ", Place=" + i);
415 + " is in bad place! Fragment=" + selectedHashFragment + ", Place=" + i);
416 } 443 }
417 } 444 }
418 } 445 }
419 // check subnodes 446 // check sub-nodes
420 for (int i = 0; i < FACTOR; i++) { 447 for (int i = 0; i < FACTOR; i++) {
421 if (this.content[2 * i + 1] != null && this.content[2 * i] == null) { 448 if (this.content[2 * i + 1] != null && this.content[2 * i] == null) {
422 @SuppressWarnings("unchecked") 449 @SuppressWarnings("unchecked") var subNode = (Node<K, V>) this.content[2 * i + 1];
423 var subNode = (Node<K, V>) this.content[2 * i + 1]; 450 subNode.checkIntegrity(hashProvider, defaultValue, incrementDepth(depth));
424 subNode.checkIntegrity(hashProvider, defaultValue, depth + 1);
425 } 451 }
426 } 452 }
427 // check the hash 453 // check the hash
428 int oldHash = this.cachedHash; 454 if (cachedHashValid) {
429 updateHash(); 455 int oldHash = this.cachedHash;
430 int newHash = this.cachedHash; 456 invalidateHash();
431 if (oldHash != newHash) { 457 int newHash = hashCode();
432 throw new IllegalStateException("Hash code was not up to date! (old=" + oldHash + ",new=" + newHash + ")"); 458 if (oldHash != newHash) {
459 throw new IllegalStateException("Hash code was not up to date! (old=" + oldHash + ",new=" + newHash + ")");
460 }
433 } 461 }
434 } 462 }
435 463
436 protected void updateHash() { 464 protected void invalidateHash() {
437 this.cachedHash = Arrays.hashCode(content); 465 this.cachedHashValid = false;
438 } 466 }
439 467
440 @Override 468 @Override
441 public int hashCode() { 469 public int hashCode() {
470 if (!this.cachedHashValid) {
471 this.cachedHash = Arrays.hashCode(content);
472 this.cachedHashValid = true;
473 }
442 return this.cachedHash; 474 return this.cachedHash;
443 } 475 }
444 476
445 @Override 477 @Override
446 public boolean equals(Object obj) { 478 public boolean equals(Object obj) {
447 if (this == obj) 479 if (this == obj) return true;
448 return true; 480 if (obj == null) return false;
449 if (obj == null)
450 return false;
451 if (obj instanceof MutableNode<?, ?> mutableObj) { 481 if (obj instanceof MutableNode<?, ?> mutableObj) {
452 return Arrays.deepEquals(this.content, mutableObj.content); 482 if (obj.hashCode() != this.hashCode()) {
483 return false;
484 } else {
485 for (int i = 0; i < FACTOR * 2; i++) {
486 Object thisContent = this.content[i];
487 if (thisContent != null && !thisContent.equals(mutableObj.content[i])) {
488 return false;
489 }
490 }
491 return true;
492 }
453 } else if (obj instanceof ImmutableNode<?, ?> immutableObj) { 493 } else if (obj instanceof ImmutableNode<?, ?> immutableObj) {
454 return ImmutableNode.compareImmutableMutable(immutableObj, this); 494 return ImmutableNode.compareImmutableMutable(immutableObj, this);
455 } else { 495 } else {
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/Node.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/Node.java
new file mode 100644
index 00000000..0ecf2712
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/Node.java
@@ -0,0 +1,131 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.state;
7
8import java.util.Map;
9
10import tools.refinery.store.map.ContinuousHashProvider;
11
12public abstract class Node<K, V> {
13 public static final int BRANCHING_FACTOR_BITS = 5;
14 public static final int FACTOR = 1 << BRANCHING_FACTOR_BITS;
15 protected static final int NUMBER_OF_FACTORS = Integer.SIZE / BRANCHING_FACTOR_BITS;
16 protected static final int FACTOR_MASK = FACTOR - 1;
17 public static final int EFFECTIVE_BITS = BRANCHING_FACTOR_BITS * NUMBER_OF_FACTORS;
18 public static final int FACTOR_SELECTION_BITS = 32 - Integer.numberOfLeadingZeros(NUMBER_OF_FACTORS);
19 public static final int FACTOR_SELECTION_MASK = (1 << FACTOR_SELECTION_BITS) - 1;
20 public static final int INCREMENT_BIG_STEP = (1 << FACTOR_SELECTION_BITS) - NUMBER_OF_FACTORS;
21
22 /**
23 * Increments the depth of the search in the tree. The depth parameter has two
24 * components: the least few bits selects the fragment of the hashcode, the
25 * other part selects the continuous hash.
26 *
27 * @param depth parameter encoding the fragment and the depth
28 * @return new depth.
29 */
30 protected int incrementDepth(int depth) {
31 int newDepth = depth + 1;
32 if ((newDepth & FACTOR_SELECTION_MASK) == NUMBER_OF_FACTORS) {
33 newDepth += INCREMENT_BIG_STEP;
34 }
35 return newDepth;
36 }
37
38 /**
39 * Calculates the index for the continuous hash.
40 *
41 * @param depth The depth of the node in the tree.
42 * @return The index of the continuous hash.
43 */
44 protected static int hashDepth(int depth) {
45 return depth >> FACTOR_SELECTION_BITS;
46 }
47
48 /**
49 * Calculates the which segment of a single hash should be used.
50 *
51 * @param depth The depth of the node in the tree.
52 * @return The segment of a hash code.
53 */
54 protected static int shiftDepth(int depth) {
55 return depth & FACTOR_SELECTION_MASK;
56 }
57
58 /**
59 * Selects a segments from a complete hash for a given depth.
60 *
61 * @param hash A complete hash.
62 * @param shiftDepth The index of the segment.
63 * @return The segment as an integer.
64 */
65 protected static int hashFragment(int hash, int shiftDepth) {
66 if (shiftDepth < 0 || Node.NUMBER_OF_FACTORS < shiftDepth)
67 throw new IllegalArgumentException("Invalid shift depth! valid interval=[0;5], input=" + shiftDepth);
68 return (hash >>> shiftDepth * BRANCHING_FACTOR_BITS) & FACTOR_MASK;
69 }
70
71 /**
72 * Returns the hash code for a given depth. It may calculate new hash code, or
73 * reuse a hash code calculated for depth-1.
74 *
75 * @param key The key.
76 * @param hash Hash code for depth-1
77 * @param depth The depth.
78 * @return The new hash code.
79 */
80 protected int newHash(final ContinuousHashProvider<? super K> hashProvider, K key, int hash, int depth) {
81 final int shiftDepth = shiftDepth(depth);
82 if (shiftDepth == 0) {
83 final int hashDepth = hashDepth(depth);
84 if (hashDepth >= ContinuousHashProvider.MAX_PRACTICAL_DEPTH) {
85 throw new IllegalArgumentException(
86 "Key " + key + " have the clashing hashcode over the practical depth limitation ("
87 + ContinuousHashProvider.MAX_PRACTICAL_DEPTH + ")!");
88 }
89 return hashProvider.getHash(key, hashDepth);
90 } else {
91 return hash;
92 }
93 }
94
95 public abstract V getValue(K key, ContinuousHashProvider<? super K> hashProvider, V defaultValue, int hash,
96 int depth);
97
98 public abstract Node<K, V> putValue(K key, V value, OldValueBox<V> old,
99 ContinuousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth);
100
101 public abstract long getSize();
102
103 abstract MutableNode<K, V> toMutable();
104
105 public abstract ImmutableNode<K, V> toImmutable(Map<Node<K, V>, ImmutableNode<K, V>> cache);
106
107 protected abstract MutableNode<K, V> isMutable();
108
109 /**
110 * Moves a {@link MapCursor} to its next position.
111 *
112 * @param cursor the cursor
113 * @return Whether there was a next value to move on.
114 */
115 abstract boolean moveToNext(MapCursor<K, V> cursor);
116 abstract boolean moveToNextInorder(InOrderMapCursor<K, V> cursor);
117
118 ///////// FOR printing
119 public abstract void prettyPrint(StringBuilder builder, int depth, int code);
120
121
122 @Override
123 public String toString() {
124 StringBuilder stringBuilder = new StringBuilder();
125 prettyPrint(stringBuilder, 0, -1);
126 return stringBuilder.toString();
127 }
128
129 public void checkIntegrity(ContinuousHashProvider<? super K> hashProvider, V defaultValue, int depth) {
130 }
131}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/OldValueBox.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/OldValueBox.java
index 354af51d..1b0a4b0c 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/OldValueBox.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/OldValueBox.java
@@ -1,9 +1,9 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.store.map.internal; 6package tools.refinery.store.map.internal.state;
7 7
8public class OldValueBox<V>{ 8public class OldValueBox<V>{
9 V oldValue; 9 V oldValue;
@@ -20,5 +20,5 @@ public class OldValueBox<V>{
20 this.oldValue = ouldValue; 20 this.oldValue = ouldValue;
21 isSet = true; 21 isSet = true;
22 } 22 }
23 23
24} 24}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/StateBasedVersionedMapStoreFactory.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/StateBasedVersionedMapStoreFactory.java
new file mode 100644
index 00000000..ccc791a8
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/StateBasedVersionedMapStoreFactory.java
@@ -0,0 +1,43 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.state;
7
8import tools.refinery.store.map.*;
9
10import java.util.List;
11
12public class StateBasedVersionedMapStoreFactory<K, V> implements VersionedMapStoreFactory<K, V> {
13 private final V defaultValue;
14 private final ContinuousHashProvider<K> continuousHashProvider;
15 private final VersionedMapStoreStateConfiguration config;
16
17 public StateBasedVersionedMapStoreFactory(V defaultValue, Boolean transformToImmutable,
18 VersionedMapStoreFactoryBuilder.SharingStrategy sharingStrategy,
19 boolean versionFreeingEnabled,
20 ContinuousHashProvider<K> continuousHashProvider) {
21 this.defaultValue = defaultValue;
22 this.continuousHashProvider = continuousHashProvider;
23
24 this.config = new VersionedMapStoreStateConfiguration(
25 transformToImmutable,
26 sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE
27 || sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE_IN_GROUP,
28 sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE_IN_GROUP,
29 versionFreeingEnabled);
30 }
31
32 @Override
33 public VersionedMapStore<K, V> createOne() {
34 return new VersionedMapStoreStateImpl<>(continuousHashProvider, defaultValue, config);
35
36 }
37
38 @Override
39 public List<VersionedMapStore<K, V>> createGroup(int amount) {
40 return VersionedMapStoreStateImpl.createSharedVersionedMapStores(amount, continuousHashProvider, defaultValue,
41 config);
42 }
43}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStateImpl.java
index 7abece0d..57eeccf6 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStateImpl.java
@@ -1,9 +1,9 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.store.map.internal; 6package tools.refinery.store.map.internal.state;
7 7
8import tools.refinery.store.map.*; 8import tools.refinery.store.map.*;
9 9
@@ -19,19 +19,18 @@ import java.util.Objects;
19 * @param <V> 19 * @param <V>
20 * @author Oszkar Semerath 20 * @author Oszkar Semerath
21 */ 21 */
22public class VersionedMapImpl<K, V> implements VersionedMap<K, V> { 22public class VersionedMapStateImpl<K, V> implements VersionedMap<K, V> {
23 protected final VersionedMapStoreImpl<K, V> store; 23 protected final VersionedMapStoreStateImpl<K, V> store;
24
25 protected final ContinousHashProvider<K> hashProvider;
26 24
25 protected final ContinuousHashProvider<K> hashProvider;
27 protected final V defaultValue; 26 protected final V defaultValue;
28 protected Node<K, V> root; 27 protected Node<K, V> root;
29 28
30 private final OldValueBox<V> oldValueBox = new OldValueBox<>(); 29 private final OldValueBox<V> oldValueBox = new OldValueBox<>();
31 30
32 public VersionedMapImpl( 31 public VersionedMapStateImpl(
33 VersionedMapStoreImpl<K, V> store, 32 VersionedMapStoreStateImpl<K, V> store,
34 ContinousHashProvider<K> hashProvider, 33 ContinuousHashProvider<K> hashProvider,
35 V defaultValue) { 34 V defaultValue) {
36 this.store = store; 35 this.store = store;
37 this.hashProvider = hashProvider; 36 this.hashProvider = hashProvider;
@@ -39,9 +38,9 @@ public class VersionedMapImpl<K, V> implements VersionedMap<K, V> {
39 this.root = null; 38 this.root = null;
40 } 39 }
41 40
42 public VersionedMapImpl( 41 public VersionedMapStateImpl(
43 VersionedMapStoreImpl<K, V> store, 42 VersionedMapStoreStateImpl<K, V> store,
44 ContinousHashProvider<K> hashProvider, 43 ContinuousHashProvider<K> hashProvider,
45 V defaultValue, Node<K, V> data) { 44 V defaultValue, Node<K, V> data) {
46 this.store = store; 45 this.store = store;
47 this.hashProvider = hashProvider; 46 this.hashProvider = hashProvider;
@@ -49,11 +48,12 @@ public class VersionedMapImpl<K, V> implements VersionedMap<K, V> {
49 this.root = data; 48 this.root = data;
50 } 49 }
51 50
51 @Override
52 public V getDefaultValue() { 52 public V getDefaultValue() {
53 return defaultValue; 53 return defaultValue;
54 } 54 }
55 55
56 public ContinousHashProvider<K> getHashProvider() { 56 public ContinuousHashProvider<K> getHashProvider() {
57 return hashProvider; 57 return hashProvider;
58 } 58 }
59 59
@@ -80,7 +80,9 @@ public class VersionedMapImpl<K, V> implements VersionedMap<K, V> {
80 Iterator<K> keyIterator = keys.iterator(); 80 Iterator<K> keyIterator = keys.iterator();
81 Iterator<V> valueIterator = values.iterator(); 81 Iterator<V> valueIterator = values.iterator();
82 while (keyIterator.hasNext()) { 82 while (keyIterator.hasNext()) {
83 this.put(keyIterator.next(), valueIterator.next()); 83 var key = keyIterator.next();
84 var value = valueIterator.next();
85 this.put(key,value);
84 } 86 }
85 } else { 87 } else {
86 while (cursor.move()) { 88 while (cursor.move()) {
@@ -113,17 +115,16 @@ public class VersionedMapImpl<K, V> implements VersionedMap<K, V> {
113 } 115 }
114 116
115 @Override 117 @Override
116 public DiffCursor<K, V> getDiffCursor(long toVersion) { 118 public DiffCursor<K, V> getDiffCursor(Version toVersion) {
117 Cursor<K, V> fromCursor = this.getAll(); 119 InOrderMapCursor<K, V> fromCursor = new InOrderMapCursor<>(this);
118 VersionedMap<K, V> toMap = this.store.createMap(toVersion); 120 VersionedMapStateImpl<K, V> toMap = (VersionedMapStateImpl<K, V>) this.store.createMap(toVersion);
119 Cursor<K, V> toCursor = toMap.getAll(); 121 InOrderMapCursor<K, V> toCursor = new InOrderMapCursor<>(toMap);
120 return new MapDiffCursor<>(this.hashProvider, this.defaultValue, fromCursor, toCursor); 122 return new MapDiffCursor<>(this.defaultValue, fromCursor, toCursor);
121
122 } 123 }
123 124
124 125
125 @Override 126 @Override
126 public long commit() { 127 public Version commit() {
127 return this.store.commit(root, this); 128 return this.store.commit(root, this);
128 } 129 }
129 130
@@ -132,20 +133,21 @@ public class VersionedMapImpl<K, V> implements VersionedMap<K, V> {
132 } 133 }
133 134
134 @Override 135 @Override
135 public void restore(long state) { 136 public void restore(Version state) {
136 root = this.store.revert(state); 137 root = this.store.revert(state);
137 } 138 }
138 139
139 public void prettyPrint() { 140 public String prettyPrint() {
140 StringBuilder s = new StringBuilder();
141 if (this.root != null) { 141 if (this.root != null) {
142 StringBuilder s = new StringBuilder();
142 this.root.prettyPrint(s, 0, -1); 143 this.root.prettyPrint(s, 0, -1);
143 System.out.println(s.toString()); 144 return s.toString();
144 } else { 145 } else {
145 System.out.println("empty tree"); 146 return "empty tree";
146 } 147 }
147 } 148 }
148 149
150 @Override
149 public void checkIntegrity() { 151 public void checkIntegrity() {
150 if (this.root != null) { 152 if (this.root != null) {
151 this.root.checkIntegrity(hashProvider, defaultValue, 0); 153 this.root.checkIntegrity(hashProvider, defaultValue, 0);
@@ -155,11 +157,15 @@ public class VersionedMapImpl<K, V> implements VersionedMap<K, V> {
155 @Override 157 @Override
156 public int contentHashCode(ContentHashCode mode) { 158 public int contentHashCode(ContentHashCode mode) {
157 // Calculating the root hashCode is always fast, because {@link Node} caches its hashCode. 159 // Calculating the root hashCode is always fast, because {@link Node} caches its hashCode.
158 return Objects.hashCode(root); 160 if(root == null) {
161 return 0;
162 } else {
163 return root.hashCode();
164 }
159 } 165 }
160 166
161 @Override 167 @Override
162 public boolean contentEquals(AnyVersionedMap other) { 168 public boolean contentEquals(AnyVersionedMap other) {
163 return other instanceof VersionedMapImpl<?, ?> otherImpl && Objects.equals(root, otherImpl.root); 169 return other instanceof VersionedMapStateImpl<?, ?> otherImpl && Objects.equals(root, otherImpl.root);
164 } 170 }
165} 171}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreConfiguration.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateConfiguration.java
index b00cd961..6650f565 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreConfiguration.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateConfiguration.java
@@ -1,21 +1,25 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.store.map; 6package tools.refinery.store.map.internal.state;
7 7
8public class VersionedMapStoreConfiguration { 8import tools.refinery.store.map.ContinuousHashProvider;
9import tools.refinery.store.map.VersionedMapStore;
9 10
10 public VersionedMapStoreConfiguration() { 11public class VersionedMapStoreStateConfiguration {
12
13 public VersionedMapStoreStateConfiguration() {
11 14
12 } 15 }
13 public VersionedMapStoreConfiguration(boolean immutableWhenCommitting, boolean sharedNodeCacheInStore, 16 public VersionedMapStoreStateConfiguration(boolean immutableWhenCommitting, boolean sharedNodeCacheInStore,
14 boolean sharedNodeCacheInStoreGroups) { 17 boolean sharedNodeCacheInStoreGroups, boolean versionFreeingEnabled) {
15 super(); 18 super();
16 this.immutableWhenCommitting = immutableWhenCommitting; 19 this.immutableWhenCommitting = immutableWhenCommitting;
17 this.sharedNodeCacheInStore = sharedNodeCacheInStore; 20 this.sharedNodeCacheInStore = sharedNodeCacheInStore;
18 this.sharedNodeCacheInStoreGroups = sharedNodeCacheInStoreGroups; 21 this.sharedNodeCacheInStoreGroups = sharedNodeCacheInStoreGroups;
22 this.versionFreeingEnabled = versionFreeingEnabled;
19 } 23 }
20 24
21 /** 25 /**
@@ -42,12 +46,17 @@ public class VersionedMapStoreConfiguration {
42 46
43 /** 47 /**
44 * If true, all sub-nodes are cached within a group of 48 * If true, all sub-nodes are cached within a group of
45 * {@link VersionedMapStoreImpl#createSharedVersionedMapStores(int, ContinousHashProvider, Object, VersionedMapStoreConfiguration)}. 49 * {@link VersionedMapStoreStateImpl#createSharedVersionedMapStores(int, ContinuousHashProvider, Object, VersionedMapStoreStateConfiguration)}.
46 * If {@link VersionedMapStoreConfiguration#sharedNodeCacheInStore} is 50 * If {@link VersionedMapStoreStateConfiguration#sharedNodeCacheInStore} is
47 * <code>false</code>, then it has currently no impact. 51 * <code>false</code>, then it has currently no impact.
48 */ 52 */
49 private boolean sharedNodeCacheInStoreGroups = true; 53 private boolean sharedNodeCacheInStoreGroups = true;
50 public boolean isSharedNodeCacheInStoreGroups() { 54 public boolean isSharedNodeCacheInStoreGroups() {
51 return sharedNodeCacheInStoreGroups; 55 return sharedNodeCacheInStoreGroups;
52 } 56 }
57
58 private boolean versionFreeingEnabled = true;
59 public boolean isVersionFreeingEnabled() {
60 return versionFreeingEnabled;
61 }
53} 62}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateImpl.java
new file mode 100644
index 00000000..8ff3f8e7
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateImpl.java
@@ -0,0 +1,119 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.state;
7
8import tools.refinery.store.map.*;
9
10import java.util.*;
11
12public class VersionedMapStoreStateImpl<K, V> implements VersionedMapStore<K, V> {
13 // Configuration
14 private final boolean immutableWhenCommitting;
15
16 // Static data
17 protected final ContinuousHashProvider<K> hashProvider;
18 protected final V defaultValue;
19
20 protected final Map<Node<K, V>, ImmutableNode<K, V>> nodeCache;
21
22 public VersionedMapStoreStateImpl(ContinuousHashProvider<K> hashProvider, V defaultValue,
23 VersionedMapStoreStateConfiguration config) {
24 this.immutableWhenCommitting = config.isImmutableWhenCommitting();
25 this.hashProvider = hashProvider;
26 this.defaultValue = defaultValue;
27 if (config.isSharedNodeCacheInStore()) {
28 nodeCache = createNoteCache(config);
29 } else {
30 nodeCache = null;
31 }
32 }
33
34 private VersionedMapStoreStateImpl(ContinuousHashProvider<K> hashProvider, V defaultValue,
35 Map<Node<K, V>, ImmutableNode<K, V>> nodeCache, VersionedMapStoreStateConfiguration config) {
36 this.immutableWhenCommitting = config.isImmutableWhenCommitting();
37 this.hashProvider = hashProvider;
38 this.defaultValue = defaultValue;
39 this.nodeCache = nodeCache;
40 }
41
42 public VersionedMapStoreStateImpl(ContinuousHashProvider<K> hashProvider, V defaultValue) {
43 this(hashProvider, defaultValue, new VersionedMapStoreStateConfiguration());
44 }
45
46 public static <K, V> List<VersionedMapStore<K, V>> createSharedVersionedMapStores(int amount,
47 ContinuousHashProvider<K> hashProvider, V defaultValue,
48 VersionedMapStoreStateConfiguration config) {
49 List<VersionedMapStore<K, V>> result = new ArrayList<>(amount);
50 if (config.isSharedNodeCacheInStoreGroups()) {
51 Map<Node<K, V>, ImmutableNode<K, V>> nodeCache;
52 if (config.isSharedNodeCacheInStore()) {
53 nodeCache = createNoteCache(config);
54 } else {
55 nodeCache = null;
56 }
57 for (int i = 0; i < amount; i++) {
58 result.add(new VersionedMapStoreStateImpl<>(hashProvider, defaultValue, nodeCache, config));
59 }
60 } else {
61 for (int i = 0; i < amount; i++) {
62 result.add(new VersionedMapStoreStateImpl<>(hashProvider, defaultValue, config));
63 }
64 }
65 return result;
66 }
67
68 private static <K,V> Map<K,V> createNoteCache(VersionedMapStoreStateConfiguration config) {
69 if(config.isVersionFreeingEnabled()) {
70 return new WeakHashMap<>();
71 } else {
72 return new HashMap<>();
73 }
74 }
75
76 public static <K, V> List<VersionedMapStore<K, V>> createSharedVersionedMapStores(int amount,
77 ContinuousHashProvider<K> hashProvider, V defaultValue) {
78 return createSharedVersionedMapStores(amount, hashProvider, defaultValue, new VersionedMapStoreStateConfiguration());
79 }
80
81 @Override
82 public VersionedMap<K, V> createMap() {
83 return new VersionedMapStateImpl<>(this, hashProvider, defaultValue);
84 }
85
86 @Override
87 public VersionedMap<K, V> createMap(Version state) {
88 ImmutableNode<K, V> data = revert(state);
89 return new VersionedMapStateImpl<>(this, hashProvider, defaultValue, data);
90 }
91
92 @SuppressWarnings("unchecked")
93 public synchronized ImmutableNode<K, V> revert(Version state) {
94 return (ImmutableNode<K, V>) state;
95 }
96
97 public synchronized Version commit(Node<K, V> data, VersionedMapStateImpl<K, V> mapToUpdateRoot) {
98 ImmutableNode<K, V> immutable;
99 if (data != null) {
100 immutable = data.toImmutable(this.nodeCache);
101 } else {
102 immutable = null;
103 }
104
105 if (this.immutableWhenCommitting) {
106 mapToUpdateRoot.setRoot(immutable);
107 }
108 return immutable;
109 }
110
111 @Override
112 public DiffCursor<K, V> getDiffCursor(Version fromState, Version toState) {
113 VersionedMapStateImpl<K, V> map1 = (VersionedMapStateImpl<K, V>) createMap(fromState);
114 VersionedMapStateImpl<K, V> map2 = (VersionedMapStateImpl<K, V>) createMap(toState);
115 InOrderMapCursor<K, V> cursor1 = new InOrderMapCursor<>(map1);
116 InOrderMapCursor<K, V> cursor2 = new InOrderMapCursor<>(map2);
117 return new MapDiffCursor<>(this.defaultValue, cursor1, cursor2);
118 }
119}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java
index ea670cbc..1b15e4cf 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java
@@ -7,6 +7,7 @@ package tools.refinery.store.model;
7 7
8import tools.refinery.store.map.Cursor; 8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.map.DiffCursor; 9import tools.refinery.store.map.DiffCursor;
10import tools.refinery.store.map.Version;
10import tools.refinery.store.representation.Symbol; 11import tools.refinery.store.representation.Symbol;
11import tools.refinery.store.tuple.Tuple; 12import tools.refinery.store.tuple.Tuple;
12 13
@@ -24,7 +25,7 @@ public non-sealed interface Interpretation<T> extends AnyInterpretation {
24 25
25 void putAll(Cursor<Tuple, T> cursor); 26 void putAll(Cursor<Tuple, T> cursor);
26 27
27 DiffCursor<Tuple, T> getDiffCursor(long to); 28 DiffCursor<Tuple, T> getDiffCursor(Version to);
28 29
29 void addListener(InterpretationListener<T> listener, boolean alsoWhenRestoring); 30 void addListener(InterpretationListener<T> listener, boolean alsoWhenRestoring);
30 31
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/Model.java b/subprojects/store/src/main/java/tools/refinery/store/model/Model.java
index d58d91c3..e2ab72e7 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/Model.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/Model.java
@@ -6,18 +6,20 @@
6package tools.refinery.store.model; 6package tools.refinery.store.model;
7 7
8import tools.refinery.store.adapter.ModelAdapter; 8import tools.refinery.store.adapter.ModelAdapter;
9import tools.refinery.store.map.Version;
9import tools.refinery.store.map.Versioned; 10import tools.refinery.store.map.Versioned;
11import tools.refinery.store.model.internal.VersionedInterpretation;
10import tools.refinery.store.representation.AnySymbol; 12import tools.refinery.store.representation.AnySymbol;
11import tools.refinery.store.representation.Symbol; 13import tools.refinery.store.representation.Symbol;
12 14
15import java.util.Map;
13import java.util.Optional; 16import java.util.Optional;
14 17
15public interface Model extends Versioned { 18public interface Model extends Versioned {
16 long NO_STATE_ID = -1; 19 Version NO_STATE_ID = null;
17
18 ModelStore getStore(); 20 ModelStore getStore();
19 21
20 long getState(); 22 Version getState();
21 23
22 boolean hasUncommittedChanges(); 24 boolean hasUncommittedChanges();
23 25
@@ -27,7 +29,7 @@ public interface Model extends Versioned {
27 29
28 <T> Interpretation<T> getInterpretation(Symbol<T> symbol); 30 <T> Interpretation<T> getInterpretation(Symbol<T> symbol);
29 31
30 ModelDiffCursor getDiffCursor(long to); 32 ModelDiffCursor getDiffCursor(Version to);
31 33
32 <T extends ModelAdapter> Optional<T> tryGetAdapter(Class<? extends T> adapterType); 34 <T extends ModelAdapter> Optional<T> tryGetAdapter(Class<? extends T> adapterType);
33 35
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java b/subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java
index a9ad8cfd..703ee10a 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java
@@ -5,6 +5,8 @@
5 */ 5 */
6package tools.refinery.store.model; 6package tools.refinery.store.model;
7 7
8import tools.refinery.store.map.Version;
9
8public interface ModelListener { 10public interface ModelListener {
9 default void beforeCommit() { 11 default void beforeCommit() {
10 } 12 }
@@ -12,7 +14,7 @@ public interface ModelListener {
12 default void afterCommit() { 14 default void afterCommit() {
13 } 15 }
14 16
15 default void beforeRestore(long state) { 17 default void beforeRestore(Version state) {
16 } 18 }
17 19
18 default void afterRestore() { 20 default void afterRestore() {
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/ModelStore.java b/subprojects/store/src/main/java/tools/refinery/store/model/ModelStore.java
index b10eb8a4..89382b3a 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/ModelStore.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/ModelStore.java
@@ -6,23 +6,21 @@
6package tools.refinery.store.model; 6package tools.refinery.store.model;
7 7
8import tools.refinery.store.adapter.ModelStoreAdapter; 8import tools.refinery.store.adapter.ModelStoreAdapter;
9import tools.refinery.store.map.Version;
9import tools.refinery.store.model.internal.ModelStoreBuilderImpl; 10import tools.refinery.store.model.internal.ModelStoreBuilderImpl;
10import tools.refinery.store.representation.AnySymbol; 11import tools.refinery.store.representation.AnySymbol;
11 12
12import java.util.Collection; 13import java.util.Collection;
13import java.util.Optional; 14import java.util.Optional;
14import java.util.Set;
15 15
16public interface ModelStore { 16public interface ModelStore {
17 Collection<AnySymbol> getSymbols(); 17 Collection<AnySymbol> getSymbols();
18 18
19 Model createEmptyModel(); 19 Model createEmptyModel();
20 20
21 Model createModelForState(long state); 21 Model createModelForState(Version state);
22 22
23 Set<Long> getStates(); 23 ModelDiffCursor getDiffCursor(Version from, Version to);
24
25 ModelDiffCursor getDiffCursor(long from, long to);
26 24
27 <T extends ModelStoreAdapter> Optional<T> tryGetAdapter(Class<? extends T> adapterType); 25 <T extends ModelStoreAdapter> Optional<T> tryGetAdapter(Class<? extends T> adapterType);
28 26
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java b/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java
index fe1c2ab5..3c94541f 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java
@@ -5,41 +5,45 @@
5 */ 5 */
6package tools.refinery.store.model; 6package tools.refinery.store.model;
7 7
8import tools.refinery.store.map.ContinousHashProvider; 8import tools.refinery.store.map.ContinuousHashProvider;
9import tools.refinery.store.tuple.Tuple; 9import tools.refinery.store.tuple.Tuple;
10import tools.refinery.store.tuple.Tuple1;
11import tools.refinery.store.tuple.Tuple2;
10 12
11public class TupleHashProvider implements ContinousHashProvider<Tuple> { 13public class TupleHashProvider implements ContinuousHashProvider<Tuple> {
12 protected static final int[] primes = new int[] { 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 14 protected static final int[] primes = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,
13 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 15 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173,
14 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 16 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283,
15 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 17 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421,
16 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 18 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563,
17 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 19 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683,
18 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 20 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829,
19 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 1019, 1021, 21 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983,
20 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 22 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097,
21 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 23 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231,
22 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423, 1427, 1429, 24 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373,
23 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, 1523, 1531, 1543, 1549, 25 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493,
24 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 26 1499, 1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, 1619,
25 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 27 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759,
26 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 28 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907,
27 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, 2087, 2089, 29 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053,
28 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, 2221, 2237, 2239, 2243, 30 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203,
29 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, 31 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, 2309, 2311, 2333, 2339,
30 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 32 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, 2441, 2447, 2459,
31 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 33 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633,
32 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789, 2791, 34 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741,
33 2797, 2801, 2803, 2819, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, 3187, 3191, 3203, 3209, 3217, 35 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169,
34 3221, 3229, 3251, 3253, 3257, 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, 3343, 3347, 3359, 36 3181, 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, 3259, 3271, 3299, 3301, 3307, 3313, 3319,
35 3361, 3371, 3373, 3389, 3391, 3407, 3413, 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 37 3323, 3329, 3331, 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, 3433, 3449, 3457, 3461, 3463,
36 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, 38 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593,
37 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, 3733, 3739, 3761, 3767, 3769, 3779, 3793, 39 3607, 3613, 3617, 3623, 3631, 3637, 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, 3733,
38 3797, 3803, 3821, 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907, 3911 }; 40 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821, 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889,
41 3907, 3911 };
39 42
40 public static final long LARGEST_PRIME_30_BITS = 1073741789; 43 protected static final long LARGEST_PRIME_30_BITS_LONG = 1073741789;
41 44 protected static final int LARGEST_PRIME_30_BITS_INTEGER = 1073741789;
42 public static final int MAX_MODEL_SIZE = (int) LARGEST_PRIME_30_BITS; 45 protected static final int LARGEST_BINARY_INDEX_1 = LARGEST_PRIME_30_BITS_INTEGER / 4;
46 public static final int MAX_MODEL_SIZE = LARGEST_PRIME_30_BITS_INTEGER;
43 47
44 public static final TupleHashProvider INSTANCE = new TupleHashProvider(); 48 public static final TupleHashProvider INSTANCE = new TupleHashProvider();
45 49
@@ -52,15 +56,68 @@ public class TupleHashProvider implements ContinousHashProvider<Tuple> {
52 56
53 @Override 57 @Override
54 public int getHash(Tuple key, int index) { 58 public int getHash(Tuple key, int index) {
55 if (index >= primes.length) { 59 if(key instanceof Tuple1 t1) {
56 throw new IllegalArgumentException("Not enough prime numbers to support index"); 60 return t1.value0();
61 } else if(key instanceof Tuple2 t2){
62 if(index == 0) {
63 return murmur3T2(t2.value0(), t2.value1());
64 } else if(index == 1) {
65 return lagrangeT2I0Quick(t2);
66 } else if(index == 2) {
67 return lagrangeT2I1Quick(t2);
68 } else {
69 return lagrangeTXIX(key, index-1);
70 }
71 } else {
72 return lagrangeTXIX(key, index);
57 } 73 }
74
75 }
76
77 private static int lagrangeT2I0Quick(Tuple2 t2) {
78 int result = 2 * t2.value0() + t2.value1();
79 if (result > LARGEST_PRIME_30_BITS_INTEGER) {
80 return result % LARGEST_PRIME_30_BITS_INTEGER;
81 } else
82 return result;
83 }
84 private static int lagrangeT2I1Quick(Tuple2 t2) {
85 int value0 = t2.value0();
86 int value1 = t2.value1();
87 if(value0 < LARGEST_BINARY_INDEX_1 && value1 < LARGEST_BINARY_INDEX_1) {
88 return 3* value0 + value1;
89 } else {
90 return lagrangeTXIX(t2, 1);
91 }
92 }
93
94 private static int lagrangeTXIX(Tuple key, int index) {
58 long accumulator = 0; 95 long accumulator = 0;
59 final int prime = primes[index]; 96 final int prime = primes[index];
60 for (int i = 0; i < key.getSize(); i++) { 97 for (int i = 0; i < key.getSize(); i++) {
61 accumulator = (prime * accumulator + key.get(i)) % MAX_MODEL_SIZE; 98 accumulator = (prime * accumulator + key.get(i)) % LARGEST_PRIME_30_BITS_LONG;
62 } 99 }
63 100
64 return (int) accumulator; 101 return (int) accumulator;
65 } 102 }
103
104 private static int murmur3T2(int v0, int v1)
105 {
106 int h = 0;
107
108 h = murmur32Scramble(v0, h);
109 h = murmur32Scramble(v1, h);
110
111 return h;
112 }
113
114 private static int murmur32Scramble(int k, int h) {
115 k *= 0xcc9e2d51;
116 k = (k << 15) | (k >>> 17);
117 k *= 0x1b873593;
118 h ^= k;
119 h = (h << 13) | (h >>> 19);
120 h = h * 5 + 0xe6546b64;
121 return h;
122 }
66} 123}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProviderBitMagic.java b/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProviderBitMagic.java
deleted file mode 100644
index 14116a90..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProviderBitMagic.java
+++ /dev/null
@@ -1,34 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.model;
7
8import tools.refinery.store.map.ContinousHashProvider;
9import tools.refinery.store.tuple.Tuple;
10
11public class TupleHashProviderBitMagic implements ContinousHashProvider<Tuple> {
12
13 @Override
14 public int getHash(Tuple key, int index) {
15 if(key.getSize() == 1) {
16 return key.get(0);
17 }
18
19 int result = 0;
20 final int startBitIndex = index*30;
21 final int finalBitIndex = startBitIndex+30;
22 final int arity = key.getSize();
23
24 for(int i = startBitIndex; i<=finalBitIndex; i++) {
25 final int selectedKey = key.get(i%arity);
26 final int selectedPosition = 1<<(i/arity);
27 if((selectedKey&selectedPosition) != 0) {
28 result |= 1<<(i%30);
29 }
30 }
31
32 return result;
33 }
34}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/IndexedVersionedInterpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/IndexedVersionedInterpretation.java
index e0a371de..0be16f77 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/IndexedVersionedInterpretation.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/IndexedVersionedInterpretation.java
@@ -7,7 +7,6 @@ package tools.refinery.store.model.internal;
7 7
8import tools.refinery.store.map.Cursor; 8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.map.VersionedMap; 9import tools.refinery.store.map.VersionedMap;
10import tools.refinery.store.map.VersionedMapStore;
11import tools.refinery.store.representation.Symbol; 10import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.tuple.Tuple; 11import tools.refinery.store.tuple.Tuple;
13 12
@@ -16,9 +15,8 @@ import java.util.Objects;
16class IndexedVersionedInterpretation<T> extends VersionedInterpretation<T> { 15class IndexedVersionedInterpretation<T> extends VersionedInterpretation<T> {
17 private final BaseIndexer<T> indexer; 16 private final BaseIndexer<T> indexer;
18 17
19 public IndexedVersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMapStore<Tuple, T> store, 18 public IndexedVersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMap<Tuple, T> map) {
20 VersionedMap<Tuple, T> map) { 19 super(model, symbol, map);
21 super(model, symbol, store, map);
22 indexer = new BaseIndexer<>(symbol.arity(), map); 20 indexer = new BaseIndexer<>(symbol.arity(), map);
23 } 21 }
24 22
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelImpl.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelImpl.java
index c5475a1a..2b12d5a6 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelImpl.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelImpl.java
@@ -8,6 +8,7 @@ package tools.refinery.store.model.internal;
8import tools.refinery.store.adapter.AdapterUtils; 8import tools.refinery.store.adapter.AdapterUtils;
9import tools.refinery.store.adapter.ModelAdapter; 9import tools.refinery.store.adapter.ModelAdapter;
10import tools.refinery.store.map.DiffCursor; 10import tools.refinery.store.map.DiffCursor;
11import tools.refinery.store.map.Version;
11import tools.refinery.store.model.*; 12import tools.refinery.store.model.*;
12import tools.refinery.store.representation.AnySymbol; 13import tools.refinery.store.representation.AnySymbol;
13import tools.refinery.store.representation.Symbol; 14import tools.refinery.store.representation.Symbol;
@@ -17,21 +18,21 @@ import java.util.*;
17 18
18public class ModelImpl implements Model { 19public class ModelImpl implements Model {
19 private final ModelStore store; 20 private final ModelStore store;
20 private long state; 21 private Version state;
21 private Map<? extends AnySymbol, ? extends VersionedInterpretation<?>> interpretations; 22 private LinkedHashMap<? extends AnySymbol, ? extends VersionedInterpretation<?>> interpretations;
22 private final List<ModelAdapter> adapters; 23 private final List<ModelAdapter> adapters;
23 private final List<ModelListener> listeners = new ArrayList<>(); 24 private final List<ModelListener> listeners = new ArrayList<>();
24 private boolean uncommittedChanges; 25 private boolean uncommittedChanges;
25 private ModelAction pendingAction = ModelAction.NONE; 26 private ModelAction pendingAction = ModelAction.NONE;
26 private long restoringToState = NO_STATE_ID; 27 private Version restoringToState = null;
27 28
28 ModelImpl(ModelStore store, long state, int adapterCount) { 29 ModelImpl(ModelStore store, Version state, int adapterCount) {
29 this.store = store; 30 this.store = store;
30 this.state = state; 31 this.state = state;
31 adapters = new ArrayList<>(adapterCount); 32 adapters = new ArrayList<>(adapterCount);
32 } 33 }
33 34
34 void setInterpretations(Map<? extends AnySymbol, ? extends VersionedInterpretation<?>> interpretations) { 35 void setInterpretations(LinkedHashMap<? extends AnySymbol, ? extends VersionedInterpretation<?>> interpretations) {
35 this.interpretations = interpretations; 36 this.interpretations = interpretations;
36 } 37 }
37 38
@@ -41,7 +42,7 @@ public class ModelImpl implements Model {
41 } 42 }
42 43
43 @Override 44 @Override
44 public long getState() { 45 public Version getState() {
45 return state; 46 return state;
46 } 47 }
47 48
@@ -57,7 +58,7 @@ public class ModelImpl implements Model {
57 } 58 }
58 59
59 @Override 60 @Override
60 public ModelDiffCursor getDiffCursor(long to) { 61 public ModelDiffCursor getDiffCursor(Version to) {
61 var diffCursors = new HashMap<AnySymbol, DiffCursor<Tuple, ?>>(interpretations.size()); 62 var diffCursors = new HashMap<AnySymbol, DiffCursor<Tuple, ?>>(interpretations.size());
62 for (var entry : interpretations.entrySet()) { 63 for (var entry : interpretations.entrySet()) {
63 diffCursors.put(entry.getKey(), entry.getValue().getDiffCursor(to)); 64 diffCursors.put(entry.getKey(), entry.getValue().getDiffCursor(to));
@@ -65,7 +66,7 @@ public class ModelImpl implements Model {
65 return new ModelDiffCursor(diffCursors); 66 return new ModelDiffCursor(diffCursors);
66 } 67 }
67 68
68 private void setState(long state) { 69 private void setState(Version state) {
69 this.state = state; 70 this.state = state;
70 uncommittedChanges = false; 71 uncommittedChanges = false;
71 } 72 }
@@ -82,11 +83,11 @@ public class ModelImpl implements Model {
82 } 83 }
83 84
84 private boolean hasPendingAction() { 85 private boolean hasPendingAction() {
85 return pendingAction != ModelAction.NONE || restoringToState != NO_STATE_ID; 86 return pendingAction != ModelAction.NONE || restoringToState != null;
86 } 87 }
87 88
88 @Override 89 @Override
89 public long commit() { 90 public Version commit() {
90 if (hasPendingAction()) { 91 if (hasPendingAction()) {
91 throw pendingActionError("commit"); 92 throw pendingActionError("commit");
92 } 93 }
@@ -94,43 +95,40 @@ public class ModelImpl implements Model {
94 try { 95 try {
95 int listenerCount = listeners.size(); 96 int listenerCount = listeners.size();
96 int i = listenerCount; 97 int i = listenerCount;
97 long version = 0; 98
99 // Before commit message to listeners
98 while (i > 0) { 100 while (i > 0) {
99 i--; 101 i--;
100 listeners.get(i).beforeCommit(); 102 listeners.get(i).beforeCommit();
101 } 103 }
102 boolean versionSet = false; 104
103 for (var interpretation : interpretations.values()) { 105 // Doing the commit on the interpretations
104 long newVersion = interpretation.commit(); 106 Version[] interpretationVersions = new Version[interpretations.size()];
105 if (versionSet) { 107 int j = 0;
106 if (version != newVersion) { 108 for (var interpretationEntry : interpretations.entrySet()) {
107 throw new IllegalStateException("Interpretations in model have different versions (%d and %d)" 109 interpretationVersions[j++] = interpretationEntry.getValue().commit();
108 .formatted(version, newVersion));
109 }
110 } else {
111 version = newVersion;
112 versionSet = true;
113 }
114 } 110 }
115 setState(version); 111 ModelVersion modelVersion = new ModelVersion(interpretationVersions);
112 setState(modelVersion);
113
114 // After commit message to listeners
116 while (i < listenerCount) { 115 while (i < listenerCount) {
117 listeners.get(i).afterCommit(); 116 listeners.get(i).afterCommit();
118 i++; 117 i++;
119 } 118 }
120 return version; 119
120 return modelVersion;
121 } finally { 121 } finally {
122 pendingAction = ModelAction.NONE; 122 pendingAction = ModelAction.NONE;
123 } 123 }
124 } 124 }
125 125
126 @Override 126 @Override
127 public void restore(long version) { 127 public void restore(Version version) {
128 if (hasPendingAction()) { 128 if (hasPendingAction()) {
129 throw pendingActionError("restore to %d".formatted(version)); 129 throw pendingActionError("restore to %s".formatted(version));
130 }
131 if (!store.getStates().contains(version)) {
132 throw new IllegalArgumentException("Store does not contain state %d".formatted(version));
133 } 130 }
131
134 pendingAction = ModelAction.RESTORE; 132 pendingAction = ModelAction.RESTORE;
135 restoringToState = version; 133 restoringToState = version;
136 try { 134 try {
@@ -140,9 +138,11 @@ public class ModelImpl implements Model {
140 i--; 138 i--;
141 listeners.get(i).beforeRestore(version); 139 listeners.get(i).beforeRestore(version);
142 } 140 }
141 int j = 0;
143 for (var interpretation : interpretations.values()) { 142 for (var interpretation : interpretations.values()) {
144 interpretation.restore(version); 143 interpretation.restore(ModelVersion.getInternalVersion(version, j++));
145 } 144 }
145
146 setState(version); 146 setState(version);
147 while (i < listenerCount) { 147 while (i < listenerCount) {
148 listeners.get(i).afterRestore(); 148 listeners.get(i).afterRestore();
@@ -150,7 +150,7 @@ public class ModelImpl implements Model {
150 } 150 }
151 } finally { 151 } finally {
152 pendingAction = ModelAction.NONE; 152 pendingAction = ModelAction.NONE;
153 restoringToState = NO_STATE_ID; 153 restoringToState = null;
154 } 154 }
155 } 155 }
156 156
@@ -159,7 +159,7 @@ public class ModelImpl implements Model {
159 case NONE -> throw new IllegalArgumentException("Trying to throw pending action error when there is no " + 159 case NONE -> throw new IllegalArgumentException("Trying to throw pending action error when there is no " +
160 "pending action"); 160 "pending action");
161 case COMMIT -> "commit"; 161 case COMMIT -> "commit";
162 case RESTORE -> "restore to %d".formatted(restoringToState); 162 case RESTORE -> "restore to %s".formatted(restoringToState);
163 }; 163 };
164 return new IllegalStateException("Cannot %s due to pending %s".formatted(currentActionName, pendingActionName)); 164 return new IllegalStateException("Cannot %s due to pending %s".formatted(currentActionName, pendingActionName));
165 } 165 }
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java
index 5c688178..2dde7a4c 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java
@@ -8,11 +8,11 @@ package tools.refinery.store.model.internal;
8import tools.refinery.store.adapter.AdapterUtils; 8import tools.refinery.store.adapter.AdapterUtils;
9import tools.refinery.store.adapter.ModelAdapterBuilder; 9import tools.refinery.store.adapter.ModelAdapterBuilder;
10import tools.refinery.store.map.VersionedMapStore; 10import tools.refinery.store.map.VersionedMapStore;
11import tools.refinery.store.map.VersionedMapStoreImpl; 11import tools.refinery.store.map.VersionedMapStoreFactory;
12import tools.refinery.store.map.VersionedMapStoreFactoryBuilder;
12import tools.refinery.store.model.ModelStore; 13import tools.refinery.store.model.ModelStore;
13import tools.refinery.store.model.ModelStoreBuilder; 14import tools.refinery.store.model.ModelStoreBuilder;
14import tools.refinery.store.model.ModelStoreConfiguration; 15import tools.refinery.store.model.ModelStoreConfiguration;
15import tools.refinery.store.model.TupleHashProvider;
16import tools.refinery.store.representation.AnySymbol; 16import tools.refinery.store.representation.AnySymbol;
17import tools.refinery.store.representation.Symbol; 17import tools.refinery.store.representation.Symbol;
18import tools.refinery.store.tuple.Tuple; 18import tools.refinery.store.tuple.Tuple;
@@ -20,8 +20,8 @@ import tools.refinery.store.tuple.Tuple;
20import java.util.*; 20import java.util.*;
21 21
22public class ModelStoreBuilderImpl implements ModelStoreBuilder { 22public class ModelStoreBuilderImpl implements ModelStoreBuilder {
23 private final Set<AnySymbol> allSymbols = new HashSet<>(); 23 private final LinkedHashSet<AnySymbol> allSymbols = new LinkedHashSet<>();
24 private final Map<SymbolEquivalenceClass<?>, List<AnySymbol>> equivalenceClasses = new HashMap<>(); 24 private final LinkedHashMap<SymbolEquivalenceClass<?>, List<AnySymbol>> equivalenceClasses = new LinkedHashMap<>();
25 private final List<ModelAdapterBuilder> adapters = new ArrayList<>(); 25 private final List<ModelAdapterBuilder> adapters = new ArrayList<>();
26 26
27 @Override 27 @Override
@@ -71,7 +71,7 @@ public class ModelStoreBuilderImpl implements ModelStoreBuilder {
71 for (int i = adapters.size() - 1; i >= 0; i--) { 71 for (int i = adapters.size() - 1; i >= 0; i--) {
72 adapters.get(i).configure(this); 72 adapters.get(i).configure(this);
73 } 73 }
74 var stores = new HashMap<AnySymbol, VersionedMapStore<Tuple, ?>>(allSymbols.size()); 74 var stores = new LinkedHashMap<AnySymbol, VersionedMapStore<Tuple, ?>>(allSymbols.size());
75 for (var entry : equivalenceClasses.entrySet()) { 75 for (var entry : equivalenceClasses.entrySet()) {
76 createStores(stores, entry.getKey(), entry.getValue()); 76 createStores(stores, entry.getKey(), entry.getValue());
77 } 77 }
@@ -86,8 +86,12 @@ public class ModelStoreBuilderImpl implements ModelStoreBuilder {
86 private <T> void createStores(Map<AnySymbol, VersionedMapStore<Tuple, ?>> stores, 86 private <T> void createStores(Map<AnySymbol, VersionedMapStore<Tuple, ?>> stores,
87 SymbolEquivalenceClass<T> equivalenceClass, List<AnySymbol> symbols) { 87 SymbolEquivalenceClass<T> equivalenceClass, List<AnySymbol> symbols) {
88 int size = symbols.size(); 88 int size = symbols.size();
89 var storeGroup = VersionedMapStoreImpl.createSharedVersionedMapStores(size, TupleHashProvider.INSTANCE, 89 VersionedMapStoreFactory<Tuple,T> mapFactory = VersionedMapStore
90 equivalenceClass.defaultValue()); 90 .<Tuple,T>builder()
91 .strategy(VersionedMapStoreFactoryBuilder.StoreStrategy.DELTA)
92 .defaultValue(equivalenceClass.defaultValue())
93 .build();
94 var storeGroup = mapFactory.createGroup(size);
91 for (int i = 0; i < size; i++) { 95 for (int i = 0; i < size; i++) {
92 stores.put(symbols.get(i), storeGroup.get(i)); 96 stores.put(symbols.get(i), storeGroup.get(i));
93 } 97 }
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java
index 60b735e6..a320a618 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java
@@ -8,8 +8,8 @@ package tools.refinery.store.model.internal;
8import tools.refinery.store.adapter.AdapterUtils; 8import tools.refinery.store.adapter.AdapterUtils;
9import tools.refinery.store.adapter.ModelStoreAdapter; 9import tools.refinery.store.adapter.ModelStoreAdapter;
10import tools.refinery.store.map.DiffCursor; 10import tools.refinery.store.map.DiffCursor;
11import tools.refinery.store.map.Version;
11import tools.refinery.store.map.VersionedMapStore; 12import tools.refinery.store.map.VersionedMapStore;
12import tools.refinery.store.model.Model;
13import tools.refinery.store.model.ModelDiffCursor; 13import tools.refinery.store.model.ModelDiffCursor;
14import tools.refinery.store.model.ModelStore; 14import tools.refinery.store.model.ModelStore;
15import tools.refinery.store.representation.AnySymbol; 15import tools.refinery.store.representation.AnySymbol;
@@ -18,10 +18,10 @@ import tools.refinery.store.tuple.Tuple;
18import java.util.*; 18import java.util.*;
19 19
20public class ModelStoreImpl implements ModelStore { 20public class ModelStoreImpl implements ModelStore {
21 private final Map<? extends AnySymbol, ? extends VersionedMapStore<Tuple, ?>> stores; 21 private final LinkedHashMap<? extends AnySymbol, ? extends VersionedMapStore<Tuple, ?>> stores;
22 private final List<ModelStoreAdapter> adapters; 22 private final List<ModelStoreAdapter> adapters;
23 23
24 ModelStoreImpl(Map<? extends AnySymbol, ? extends VersionedMapStore<Tuple, ?>> stores, int adapterCount) { 24 ModelStoreImpl(LinkedHashMap<? extends AnySymbol, ? extends VersionedMapStore<Tuple, ?>> stores, int adapterCount) {
25 this.stores = stores; 25 this.stores = stores;
26 adapters = new ArrayList<>(adapterCount); 26 adapters = new ArrayList<>(adapterCount);
27 } 27 }
@@ -31,14 +31,14 @@ public class ModelStoreImpl implements ModelStore {
31 return Collections.unmodifiableCollection(stores.keySet()); 31 return Collections.unmodifiableCollection(stores.keySet());
32 } 32 }
33 33
34 private ModelImpl createModelWithoutInterpretations(long state) { 34 private ModelImpl createModelWithoutInterpretations(Version state) {
35 return new ModelImpl(this, state, adapters.size()); 35 return new ModelImpl(this, state, adapters.size());
36 } 36 }
37 37
38 @Override 38 @Override
39 public ModelImpl createEmptyModel() { 39 public ModelImpl createEmptyModel() {
40 var model = createModelWithoutInterpretations(Model.NO_STATE_ID); 40 var model = createModelWithoutInterpretations(null);
41 var interpretations = new HashMap<AnySymbol, VersionedInterpretation<?>>(stores.size()); 41 var interpretations = new LinkedHashMap<AnySymbol, VersionedInterpretation<?>>(stores.size());
42 for (var entry : this.stores.entrySet()) { 42 for (var entry : this.stores.entrySet()) {
43 var symbol = entry.getKey(); 43 var symbol = entry.getKey();
44 interpretations.put(symbol, VersionedInterpretation.of(model, symbol, entry.getValue())); 44 interpretations.put(symbol, VersionedInterpretation.of(model, symbol, entry.getValue()));
@@ -49,13 +49,21 @@ public class ModelStoreImpl implements ModelStore {
49 } 49 }
50 50
51 @Override 51 @Override
52 public synchronized ModelImpl createModelForState(long state) { 52 public synchronized ModelImpl createModelForState(Version state) {
53 var model = createModelWithoutInterpretations(state); 53 var model = createModelWithoutInterpretations(state);
54 var interpretations = new HashMap<AnySymbol, VersionedInterpretation<?>>(stores.size()); 54 var interpretations = new LinkedHashMap<AnySymbol, VersionedInterpretation<?>>(stores.size());
55
56 int i=0;
55 for (var entry : this.stores.entrySet()) { 57 for (var entry : this.stores.entrySet()) {
56 var symbol = entry.getKey(); 58 var symbol = entry.getKey();
57 interpretations.put(symbol, VersionedInterpretation.of(model, symbol, entry.getValue(), state)); 59 interpretations.put(symbol,
60 VersionedInterpretation.of(
61 model,
62 symbol,
63 entry.getValue(),
64 ModelVersion.getInternalVersion(state,i++)));
58 } 65 }
66
59 model.setInterpretations(interpretations); 67 model.setInterpretations(interpretations);
60 adaptModel(model); 68 adaptModel(model);
61 return model; 69 return model;
@@ -69,16 +77,7 @@ public class ModelStoreImpl implements ModelStore {
69 } 77 }
70 78
71 @Override 79 @Override
72 public synchronized Set<Long> getStates() { 80 public synchronized ModelDiffCursor getDiffCursor(Version from, Version to) {
73 var iterator = stores.values().iterator();
74 if (iterator.hasNext()) {
75 return Set.copyOf(iterator.next().getStates());
76 }
77 return Set.of(0L);
78 }
79
80 @Override
81 public synchronized ModelDiffCursor getDiffCursor(long from, long to) {
82 var diffCursors = new HashMap<AnySymbol, DiffCursor<?, ?>>(); 81 var diffCursors = new HashMap<AnySymbol, DiffCursor<?, ?>>();
83 for (var entry : stores.entrySet()) { 82 for (var entry : stores.entrySet()) {
84 var representation = entry.getKey(); 83 var representation = entry.getKey();
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelVersion.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelVersion.java
new file mode 100644
index 00000000..c3e52084
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelVersion.java
@@ -0,0 +1,29 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.model.internal;
7
8import tools.refinery.store.map.Version;
9
10import java.util.Arrays;
11
12public class ModelVersion implements Version {
13 final Version[] mapVersions;
14
15 public ModelVersion(Version[] mapVersions) {
16 this.mapVersions = mapVersions;
17 }
18
19 public static Version getInternalVersion(Version modelVersion, int interpretationIndex) {
20 return ((ModelVersion) modelVersion).mapVersions[interpretationIndex];
21 }
22
23 @Override
24 public String toString() {
25 return "ModelVersion{" +
26 "mapVersions=" + Arrays.toString(mapVersions) +
27 '}';
28 }
29}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/NullaryVersionedInterpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/NullaryVersionedInterpretation.java
index 96639a8e..4a8e6752 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/NullaryVersionedInterpretation.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/NullaryVersionedInterpretation.java
@@ -7,14 +7,12 @@ package tools.refinery.store.model.internal;
7 7
8import tools.refinery.store.map.Cursor; 8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.map.VersionedMap; 9import tools.refinery.store.map.VersionedMap;
10import tools.refinery.store.map.VersionedMapStore;
11import tools.refinery.store.representation.Symbol; 10import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.tuple.Tuple; 11import tools.refinery.store.tuple.Tuple;
13 12
14class NullaryVersionedInterpretation<T> extends VersionedInterpretation<T> { 13class NullaryVersionedInterpretation<T> extends VersionedInterpretation<T> {
15 public NullaryVersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMapStore<Tuple, T> store, 14 public NullaryVersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMap<Tuple, T> map) {
16 VersionedMap<Tuple, T> map) { 15 super(model, symbol, map);
17 super(model, symbol, store, map);
18 } 16 }
19 17
20 @Override 18 @Override
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/UnaryVersionedInterpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/UnaryVersionedInterpretation.java
index 4ec04358..75946680 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/UnaryVersionedInterpretation.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/UnaryVersionedInterpretation.java
@@ -8,16 +8,14 @@ package tools.refinery.store.model.internal;
8import tools.refinery.store.map.Cursor; 8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.map.Cursors; 9import tools.refinery.store.map.Cursors;
10import tools.refinery.store.map.VersionedMap; 10import tools.refinery.store.map.VersionedMap;
11import tools.refinery.store.map.VersionedMapStore;
12import tools.refinery.store.representation.Symbol; 11import tools.refinery.store.representation.Symbol;
13import tools.refinery.store.tuple.Tuple; 12import tools.refinery.store.tuple.Tuple;
14 13
15import java.util.Objects; 14import java.util.Objects;
16 15
17class UnaryVersionedInterpretation<T> extends VersionedInterpretation<T> { 16class UnaryVersionedInterpretation<T> extends VersionedInterpretation<T> {
18 public UnaryVersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMapStore<Tuple, T> store, 17 public UnaryVersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMap<Tuple, T> map) {
19 VersionedMap<Tuple, T> map) { 18 super(model, symbol, map);
20 super(model, symbol, store, map);
21 } 19 }
22 20
23 private void validateSlot(int slot) { 21 private void validateSlot(int slot) {
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java
index 877028cc..71df3962 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java
@@ -5,15 +5,10 @@
5 */ 5 */
6package tools.refinery.store.model.internal; 6package tools.refinery.store.model.internal;
7 7
8import tools.refinery.store.map.Cursor; 8import tools.refinery.store.map.*;
9import tools.refinery.store.map.DiffCursor;
10import tools.refinery.store.map.VersionedMap;
11import tools.refinery.store.map.VersionedMapStore;
12import tools.refinery.store.map.internal.MapDiffCursor;
13import tools.refinery.store.model.Interpretation; 9import tools.refinery.store.model.Interpretation;
14import tools.refinery.store.model.InterpretationListener; 10import tools.refinery.store.model.InterpretationListener;
15import tools.refinery.store.model.Model; 11import tools.refinery.store.model.Model;
16import tools.refinery.store.model.TupleHashProvider;
17import tools.refinery.store.representation.AnySymbol; 12import tools.refinery.store.representation.AnySymbol;
18import tools.refinery.store.representation.Symbol; 13import tools.refinery.store.representation.Symbol;
19import tools.refinery.store.tuple.Tuple; 14import tools.refinery.store.tuple.Tuple;
@@ -24,16 +19,13 @@ import java.util.List;
24public abstract class VersionedInterpretation<T> implements Interpretation<T> { 19public abstract class VersionedInterpretation<T> implements Interpretation<T> {
25 private final ModelImpl model; 20 private final ModelImpl model;
26 private final Symbol<T> symbol; 21 private final Symbol<T> symbol;
27 private final VersionedMapStore<Tuple, T> store;
28 private final VersionedMap<Tuple, T> map; 22 private final VersionedMap<Tuple, T> map;
29 private final List<InterpretationListener<T>> listeners = new ArrayList<>(); 23 private final List<InterpretationListener<T>> listeners = new ArrayList<>();
30 private final List<InterpretationListener<T>> restoreListeners = new ArrayList<>(); 24 private final List<InterpretationListener<T>> restoreListeners = new ArrayList<>();
31 25
32 protected VersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMapStore<Tuple, T> store, 26 protected VersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMap<Tuple, T> map) {
33 VersionedMap<Tuple, T> map) {
34 this.model = model; 27 this.model = model;
35 this.symbol = symbol; 28 this.symbol = symbol;
36 this.store = store;
37 this.map = map; 29 this.map = map;
38 } 30 }
39 31
@@ -116,13 +108,11 @@ public abstract class VersionedInterpretation<T> implements Interpretation<T> {
116 } 108 }
117 109
118 @Override 110 @Override
119 public DiffCursor<Tuple, T> getDiffCursor(long to) { 111 public DiffCursor<Tuple, T> getDiffCursor(Version to) {
120 var fromCursor = getAll(); 112 return map.getDiffCursor(to);
121 var toCursor = store.createMap(to).getAll();
122 return new MapDiffCursor<>(TupleHashProvider.INSTANCE, symbol.defaultValue(), fromCursor, toCursor);
123 } 113 }
124 114
125 public long commit() { 115 Version commit() {
126 return map.commit(); 116 return map.commit();
127 } 117 }
128 118
@@ -130,7 +120,7 @@ public abstract class VersionedInterpretation<T> implements Interpretation<T> {
130 return !restoreListeners.isEmpty(); 120 return !restoreListeners.isEmpty();
131 } 121 }
132 122
133 public void restore(long state) { 123 public void restore(Version state) {
134 if (shouldNotifyRestoreListeners()) { 124 if (shouldNotifyRestoreListeners()) {
135 var diffCursor = getDiffCursor(state); 125 var diffCursor = getDiffCursor(state);
136 while (diffCursor.move()) { 126 while (diffCursor.move()) {
@@ -158,23 +148,23 @@ public abstract class VersionedInterpretation<T> implements Interpretation<T> {
158 @SuppressWarnings("unchecked") 148 @SuppressWarnings("unchecked")
159 var typedSymbol = (Symbol<T>) symbol; 149 var typedSymbol = (Symbol<T>) symbol;
160 var map = store.createMap(); 150 var map = store.createMap();
161 return of(model, typedSymbol, store, map); 151 return of(model, typedSymbol, map);
162 } 152 }
163 153
164 static <T> VersionedInterpretation<T> of(ModelImpl model, AnySymbol symbol, VersionedMapStore<Tuple, T> store, 154 static <T> VersionedInterpretation<T> of(ModelImpl model, AnySymbol symbol, VersionedMapStore<Tuple, T> store,
165 long state) { 155 Version state) {
166 @SuppressWarnings("unchecked") 156 @SuppressWarnings("unchecked")
167 var typedSymbol = (Symbol<T>) symbol; 157 var typedSymbol = (Symbol<T>) symbol;
168 var map = store.createMap(state); 158 var map = store.createMap(state);
169 return of(model, typedSymbol, store, map); 159 return of(model, typedSymbol, map);
170 } 160 }
171 161
172 private static <T> VersionedInterpretation<T> of(ModelImpl model, Symbol<T> typedSymbol, 162 private static <T> VersionedInterpretation<T> of(ModelImpl model, Symbol<T> typedSymbol,
173 VersionedMapStore<Tuple, T> store, VersionedMap<Tuple, T> map) { 163 VersionedMap<Tuple, T> map) {
174 return switch (typedSymbol.arity()) { 164 return switch (typedSymbol.arity()) {
175 case 0 -> new NullaryVersionedInterpretation<>(model, typedSymbol, store, map); 165 case 0 -> new NullaryVersionedInterpretation<>(model, typedSymbol, map);
176 case 1 -> new UnaryVersionedInterpretation<>(model, typedSymbol, store, map); 166 case 1 -> new UnaryVersionedInterpretation<>(model, typedSymbol, map);
177 default -> new IndexedVersionedInterpretation<>(model, typedSymbol, store, map); 167 default -> new IndexedVersionedInterpretation<>(model, typedSymbol, map);
178 }; 168 };
179 } 169 }
180} 170}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/Morphism.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/Morphism.java
new file mode 100644
index 00000000..d4ac3b58
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/Morphism.java
@@ -0,0 +1,11 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8public interface Morphism {
9 int get(int object);
10 int getSize();
11}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/ObjectCode.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/ObjectCode.java
new file mode 100644
index 00000000..840253ea
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/ObjectCode.java
@@ -0,0 +1,11 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8public interface ObjectCode {
9 long get(int object);
10 int getSize();
11}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculator.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculator.java
new file mode 100644
index 00000000..b7f1d81a
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculator.java
@@ -0,0 +1,10 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8public interface StateCodeCalculator {
9 StateCoderResult calculateCodes();
10}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculatorFactory.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculatorFactory.java
new file mode 100644
index 00000000..04e17a13
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculatorFactory.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8import org.eclipse.collections.api.set.primitive.IntSet;
9import tools.refinery.store.model.Interpretation;
10
11import java.util.List;
12
13public interface StateCodeCalculatorFactory {
14 StateCodeCalculator create(List<? extends Interpretation<?>> interpretations, IntSet individuals);
15}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderAdapter.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderAdapter.java
new file mode 100644
index 00000000..cb73d27e
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderAdapter.java
@@ -0,0 +1,23 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8import tools.refinery.store.adapter.ModelAdapter;
9import tools.refinery.store.statecoding.internal.StateCoderBuilderImpl;
10
11public interface StateCoderAdapter extends ModelAdapter {
12 StateCoderResult calculateStateCode();
13 default int calculateModelCode() {
14 return calculateStateCode().modelCode();
15 }
16 default ObjectCode calculateObjectCode() {
17 return calculateStateCode().objectCode();
18 }
19
20 static StateCoderBuilder builder() {
21 return new StateCoderBuilderImpl();
22 }
23}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderBuilder.java
new file mode 100644
index 00000000..54650825
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderBuilder.java
@@ -0,0 +1,45 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8import tools.refinery.store.adapter.ModelAdapterBuilder;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.representation.AnySymbol;
11import tools.refinery.store.tuple.Tuple1;
12
13import java.util.Arrays;
14import java.util.Collection;
15import java.util.List;
16
17public interface StateCoderBuilder extends ModelAdapterBuilder {
18 StateCoderBuilder exclude(AnySymbol symbol);
19 default StateCoderBuilder excludeAll(Collection<? extends AnySymbol> symbols) {
20 for(var symbol : symbols) {
21 exclude(symbol);
22 }
23 return this;
24 }
25 default StateCoderBuilder excludeAll(AnySymbol... symbols) {
26 return excludeAll(List.of(symbols));
27 }
28
29 StateCoderBuilder individual(Tuple1 tuple);
30 default StateCoderBuilder individual(Collection<Tuple1> tuple1s) {
31 for(Tuple1 tuple : tuple1s){
32 individual(tuple);
33 }
34 return this;
35 }
36 default StateCoderBuilder individuals(Tuple1... tuple1s) {
37 return individual(Arrays.stream(tuple1s).toList());
38 }
39
40 StateCoderBuilder stateCodeCalculatorFactory(StateCodeCalculatorFactory codeCalculatorFactory);
41 StateCoderBuilder stateEquivalenceChecker(StateEquivalenceChecker stateEquivalenceChecker);
42
43 @Override
44 StateCoderStoreAdapter build(ModelStore store);
45}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderResult.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderResult.java
new file mode 100644
index 00000000..d2aff836
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderResult.java
@@ -0,0 +1,9 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8public record StateCoderResult(int modelCode, ObjectCode objectCode) {
9}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderStoreAdapter.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderStoreAdapter.java
new file mode 100644
index 00000000..c6509521
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderStoreAdapter.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8import tools.refinery.store.adapter.ModelStoreAdapter;
9import tools.refinery.store.map.Version;
10import tools.refinery.store.model.Model;
11
12public interface StateCoderStoreAdapter extends ModelStoreAdapter {
13 StateEquivalenceChecker.EquivalenceResult checkEquivalence(Version v1, Version v2);
14
15 @Override
16 StateCoderAdapter createModelAdapter(Model model);
17}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java
new file mode 100644
index 00000000..0f0023ed
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8import org.eclipse.collections.api.set.primitive.IntSet;
9import tools.refinery.store.model.AnyInterpretation;
10
11import java.util.List;
12
13public interface StateEquivalenceChecker {
14 enum EquivalenceResult {
15 ISOMORPHIC, UNKNOWN, DIFFERENT
16 }
17
18 EquivalenceResult constructMorphism(
19 IntSet individuals, List<? extends AnyInterpretation> interpretations1, ObjectCode code1,
20 List<? extends AnyInterpretation> interpretations2, ObjectCode code2);
21}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderAdapterImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderAdapterImpl.java
new file mode 100644
index 00000000..a2471916
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderAdapterImpl.java
@@ -0,0 +1,39 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.internal;
7
8import tools.refinery.store.adapter.ModelStoreAdapter;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.statecoding.StateCodeCalculator;
11import tools.refinery.store.statecoding.StateCoderAdapter;
12import tools.refinery.store.statecoding.StateCoderResult;
13
14public class StateCoderAdapterImpl implements StateCoderAdapter {
15 final ModelStoreAdapter storeAdapter;
16 final Model model;
17 final StateCodeCalculator calculator;
18
19 StateCoderAdapterImpl(ModelStoreAdapter storeAdapter, StateCodeCalculator calculator, Model model) {
20 this.storeAdapter = storeAdapter;
21 this.model = model;
22 this.calculator = calculator;
23 }
24
25 @Override
26 public Model getModel() {
27 return model;
28 }
29
30 @Override
31 public ModelStoreAdapter getStoreAdapter() {
32 return storeAdapter;
33 }
34
35 @Override
36 public StateCoderResult calculateStateCode() {
37 return calculator.calculateCodes();
38 }
39}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderBuilderImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderBuilderImpl.java
new file mode 100644
index 00000000..eed591e7
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderBuilderImpl.java
@@ -0,0 +1,71 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.internal;
7
8import org.eclipse.collections.api.factory.primitive.IntSets;
9import org.eclipse.collections.api.set.primitive.MutableIntSet;
10import tools.refinery.store.adapter.AbstractModelAdapterBuilder;
11import tools.refinery.store.model.ModelStore;
12import tools.refinery.store.representation.AnySymbol;
13import tools.refinery.store.representation.Symbol;
14import tools.refinery.store.statecoding.StateCodeCalculatorFactory;
15import tools.refinery.store.statecoding.StateCoderBuilder;
16import tools.refinery.store.statecoding.StateCoderStoreAdapter;
17import tools.refinery.store.statecoding.StateEquivalenceChecker;
18import tools.refinery.store.statecoding.neighbourhood.NeighbourhoodCalculator;
19import tools.refinery.store.statecoding.stateequivalence.StateEquivalenceCheckerImpl;
20import tools.refinery.store.tuple.Tuple1;
21
22import java.util.HashSet;
23import java.util.LinkedHashSet;
24import java.util.Set;
25
26public class StateCoderBuilderImpl extends AbstractModelAdapterBuilder<StateCoderStoreAdapter>
27 implements StateCoderBuilder {
28 private final Set<AnySymbol> excluded = new HashSet<>();
29 private final MutableIntSet individuals = IntSets.mutable.empty();
30 private StateCodeCalculatorFactory calculator = NeighbourhoodCalculator::new;
31 private StateEquivalenceChecker checker = new StateEquivalenceCheckerImpl();
32
33 @Override
34 public StateCoderBuilder exclude(AnySymbol symbol) {
35 checkNotConfigured();
36 excluded.add(symbol);
37 return this;
38 }
39
40 @Override
41 public StateCoderBuilder individual(Tuple1 tuple) {
42 checkNotConfigured();
43 individuals.add(tuple.get(0));
44 return this;
45 }
46
47 @Override
48 public StateCoderBuilder stateEquivalenceChecker(StateEquivalenceChecker stateEquivalenceChecker) {
49 checkNotConfigured();
50 this.checker = stateEquivalenceChecker;
51 return this;
52 }
53
54 @Override
55 public StateCoderBuilder stateCodeCalculatorFactory(StateCodeCalculatorFactory codeCalculatorFactory) {
56 checkNotConfigured();
57 this.calculator = codeCalculatorFactory;
58 return this;
59 }
60
61 @Override
62 protected StateCoderStoreAdapter doBuild(ModelStore store) {
63 Set<Symbol<?>> symbols = new LinkedHashSet<>();
64 for (AnySymbol symbol : store.getSymbols()) {
65 if (!excluded.contains(symbol) && (symbol instanceof Symbol<?> typed)) {
66 symbols.add(typed);
67 }
68 }
69 return new StateCoderStoreAdapterImpl(store, calculator, checker, symbols, individuals);
70 }
71}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderStoreAdapterImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderStoreAdapterImpl.java
new file mode 100644
index 00000000..89586bfb
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderStoreAdapterImpl.java
@@ -0,0 +1,74 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.internal;
7
8import org.eclipse.collections.api.set.primitive.IntSet;
9import tools.refinery.store.map.Version;
10import tools.refinery.store.model.Model;
11import tools.refinery.store.model.ModelStore;
12import tools.refinery.store.representation.Symbol;
13import tools.refinery.store.statecoding.StateCodeCalculatorFactory;
14import tools.refinery.store.statecoding.StateCoderAdapter;
15import tools.refinery.store.statecoding.StateCoderStoreAdapter;
16import tools.refinery.store.statecoding.StateEquivalenceChecker;
17
18import java.util.Collection;
19import java.util.Objects;
20
21public class StateCoderStoreAdapterImpl implements StateCoderStoreAdapter {
22 final ModelStore store;
23 final Collection<Symbol<?>> symbols;
24 final IntSet individuals;
25
26 final StateEquivalenceChecker equivalenceChecker;
27 final StateCodeCalculatorFactory codeCalculatorFactory;
28
29 StateCoderStoreAdapterImpl(ModelStore store,
30 StateCodeCalculatorFactory codeCalculatorFactory,
31 StateEquivalenceChecker equivalenceChecker,
32 Collection<Symbol<?>> symbols,
33 IntSet individuals)
34 {
35 this.codeCalculatorFactory = codeCalculatorFactory;
36 this.equivalenceChecker = equivalenceChecker;
37 this.store = store;
38 this.symbols = symbols;
39 this.individuals = individuals;
40 }
41
42 @Override
43 public ModelStore getStore() {
44 return store;
45 }
46
47 @Override
48 public StateEquivalenceChecker.EquivalenceResult checkEquivalence(Version v1, Version v2) {
49 if (Objects.equals(v1, v2)) {
50 return StateEquivalenceChecker.EquivalenceResult.ISOMORPHIC;
51 }
52 var model1 = this.getStore().createModelForState(v1);
53 var model2 = this.getStore().createModelForState(v2);
54
55 var s1 = model1.getAdapter(StateCoderAdapter.class).calculateStateCode();
56 var s2 = model2.getAdapter(StateCoderAdapter.class).calculateStateCode();
57
58 if (s1.modelCode() != s2.modelCode()) {
59 return StateEquivalenceChecker.EquivalenceResult.DIFFERENT;
60 }
61
62 var i1 = symbols.stream().map(model1::getInterpretation).toList();
63 var i2 = symbols.stream().map(model2::getInterpretation).toList();
64
65 return equivalenceChecker.constructMorphism(individuals, i1, s1.objectCode(), i2, s2.objectCode());
66 }
67
68 @Override
69 public StateCoderAdapter createModelAdapter(Model model) {
70 var interpretations = symbols.stream().map(model::getInterpretation).toList();
71 var coder = codeCalculatorFactory.create(interpretations, individuals);
72 return new StateCoderAdapterImpl(this, coder, model);
73 }
74}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java
new file mode 100644
index 00000000..5d390da2
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java
@@ -0,0 +1,96 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.neighbourhood;
7
8import org.eclipse.collections.api.factory.primitive.IntLongMaps;
9import org.eclipse.collections.api.map.primitive.MutableIntLongMap;
10import org.eclipse.collections.api.set.primitive.IntSet;
11import tools.refinery.store.model.AnyInterpretation;
12import tools.refinery.store.model.Interpretation;
13import tools.refinery.store.statecoding.ObjectCode;
14import tools.refinery.store.tuple.Tuple;
15import tools.refinery.store.tuple.Tuple0;
16
17import java.util.*;
18
19public abstract class AbstractNeighbourhoodCalculator {
20 protected final List<AnyInterpretation> nullImpactValues;
21 protected final LinkedHashMap<AnyInterpretation, long[]> impactValues;
22 protected final MutableIntLongMap individualHashValues = IntLongMaps.mutable.empty();
23
24 protected static final long PRIME = 31;
25
26 protected AbstractNeighbourhoodCalculator(List<? extends AnyInterpretation> interpretations, IntSet individuals) {
27 this.nullImpactValues = new ArrayList<>();
28 this.impactValues = new LinkedHashMap<>();
29 // Random isn't used for cryptographical purposes but just to assign distinguishable identifiers to symbols.
30 @SuppressWarnings("squid:S2245")
31 Random random = new Random(1);
32
33 var individualsInOrder = individuals.toSortedList(Integer::compare);
34 for(int i = 0; i<individualsInOrder.size(); i++) {
35 individualHashValues.put(individualsInOrder.get(i), random.nextLong());
36 }
37
38 for (AnyInterpretation interpretation : interpretations) {
39 int arity = interpretation.getSymbol().arity();
40 if (arity == 0) {
41 nullImpactValues.add(interpretation);
42 } else {
43 long[] impact = new long[arity];
44 for (int i = 0; i < arity; i++) {
45 impact[i] = random.nextInt();
46 }
47 impactValues.put(interpretation, impact);
48 }
49 }
50 }
51
52 protected void initializeWithIndividuals(ObjectCodeImpl previous) {
53 for (var entry : individualHashValues.keyValuesView()) {
54 previous.set(entry.getOne(), entry.getTwo());
55 }
56 }
57
58 protected long getTupleHash1(Tuple tuple, Object value, ObjectCode objectCodeImpl) {
59 long result = Objects.hashCode(value);
60 result = result * PRIME + objectCodeImpl.get(tuple.get(0));
61 return result;
62 }
63
64 protected long getTupleHash2(Tuple tuple, Object value, ObjectCode objectCodeImpl) {
65 long result = Objects.hashCode(value);
66 result = result * PRIME + objectCodeImpl.get(tuple.get(0));
67 result = result * PRIME + objectCodeImpl.get(tuple.get(1));
68 if (tuple.get(0) == tuple.get(1)) {
69 result += PRIME;
70 result *= PRIME;
71 }
72 return result;
73 }
74
75 protected long getTupleHashN(Tuple tuple, Object value, ObjectCode objectCodeImpl) {
76 long result = Objects.hashCode(value);
77 for (int i = 0; i < tuple.getSize(); i++) {
78 result = result * PRIME + objectCodeImpl.get(tuple.get(i));
79 }
80 return result;
81 }
82
83 protected void addHash(ObjectCodeImpl objectCodeImpl, int o, long impact, long tupleHash) {
84 long x = tupleHash * impact;
85 objectCodeImpl.set(o, objectCodeImpl.get(o) + x);
86 }
87
88 protected long calculateModelCode(long lastSum) {
89 long result = 0;
90 for (var nullImpactValue : nullImpactValues) {
91 result = result * PRIME + Objects.hashCode(((Interpretation<?>) nullImpactValue).get(Tuple0.INSTANCE));
92 }
93 result += lastSum;
94 return result;
95 }
96}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java
new file mode 100644
index 00000000..c188a839
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java
@@ -0,0 +1,193 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.neighbourhood;
7
8import org.eclipse.collections.api.factory.primitive.LongIntMaps;
9import org.eclipse.collections.api.map.primitive.LongIntMap;
10import org.eclipse.collections.api.map.primitive.MutableLongIntMap;
11import org.eclipse.collections.api.set.primitive.IntSet;
12import tools.refinery.store.map.Cursor;
13import tools.refinery.store.model.AnyInterpretation;
14import tools.refinery.store.model.Interpretation;
15import tools.refinery.store.statecoding.StateCodeCalculator;
16import tools.refinery.store.statecoding.StateCoderResult;
17import tools.refinery.store.tuple.Tuple;
18
19import java.util.List;
20
21public class LazyNeighbourhoodCalculator extends AbstractNeighbourhoodCalculator implements StateCodeCalculator {
22 public LazyNeighbourhoodCalculator(List<? extends AnyInterpretation> interpretations, IntSet individuals) {
23 super(interpretations, individuals);
24 }
25
26 public StateCoderResult calculateCodes() {
27 ObjectCodeImpl previousObjectCode = new ObjectCodeImpl();
28 MutableLongIntMap prevHash2Amount = LongIntMaps.mutable.empty();
29
30 long lastSum;
31 // All hash code is 0, except to the individuals.
32 int lastSize = 1;
33 boolean first = true;
34
35 boolean grows;
36 int rounds = 0;
37 do {
38 final ObjectCodeImpl nextObjectCode;
39 if (first) {
40 nextObjectCode = new ObjectCodeImpl();
41 initializeWithIndividuals(nextObjectCode);
42 } else {
43 nextObjectCode = new ObjectCodeImpl(previousObjectCode);
44 }
45 constructNextObjectCodes(previousObjectCode, nextObjectCode, prevHash2Amount);
46
47 MutableLongIntMap nextHash2Amount = LongIntMaps.mutable.empty();
48 lastSum = calculateLastSum(previousObjectCode, nextObjectCode, prevHash2Amount, nextHash2Amount);
49
50 int nextSize = nextHash2Amount.size();
51 grows = nextSize > lastSize;
52 lastSize = nextSize;
53 first = false;
54
55 previousObjectCode = nextObjectCode;
56 prevHash2Amount = nextHash2Amount;
57 } while (grows && rounds++ < 4/*&& lastSize < previousObjectCode.getSize()*/);
58
59 long result = calculateModelCode(lastSum);
60
61 return new StateCoderResult((int) result, previousObjectCode);
62 }
63
64 private long calculateLastSum(ObjectCodeImpl previous, ObjectCodeImpl next, LongIntMap hash2Amount,
65 MutableLongIntMap nextHash2Amount) {
66 long lastSum = 0;
67 for (int i = 0; i < next.getSize(); i++) {
68 final long hash;
69 if (isUnique(hash2Amount, previous, i)) {
70 hash = previous.get(i);
71 next.set(i, hash);
72 } else {
73 hash = next.get(i);
74 }
75
76 final int amount = nextHash2Amount.get(hash);
77 nextHash2Amount.put(hash, amount + 1);
78
79 final long shifted1 = hash >>> 8;
80 final long shifted2 = hash << 8;
81 final long shifted3 = hash >> 2;
82 lastSum += shifted1 * shifted3 + shifted2;
83 }
84 return lastSum;
85 }
86
87 private void constructNextObjectCodes(ObjectCodeImpl previous, ObjectCodeImpl next, LongIntMap hash2Amount) {
88 for (var impactValueEntry : this.impactValues.entrySet()) {
89 Interpretation<?> interpretation = (Interpretation<?>) impactValueEntry.getKey();
90 var cursor = interpretation.getAll();
91 int arity = interpretation.getSymbol().arity();
92 long[] impactValue = impactValueEntry.getValue();
93
94 if (arity == 1) {
95 while (cursor.move()) {
96 lazyImpactCalculation1(hash2Amount, previous, next, impactValue, cursor);
97 }
98 } else if (arity == 2) {
99 while (cursor.move()) {
100 lazyImpactCalculation2(hash2Amount, previous, next, impactValue, cursor);
101 }
102 } else {
103 while (cursor.move()) {
104 lazyImpactCalculationN(hash2Amount, previous, next, impactValue, cursor);
105 }
106 }
107 }
108 }
109
110 private boolean isUnique(LongIntMap hash2Amount, ObjectCodeImpl objectCodeImpl, int object) {
111 final long hash = objectCodeImpl.get(object);
112 if (hash == 0) {
113 return false;
114 }
115 final int amount = hash2Amount.get(hash);
116 return amount == 1;
117 }
118
119 private void lazyImpactCalculation1(LongIntMap hash2Amount, ObjectCodeImpl previous, ObjectCodeImpl next,
120 long[] impactValues, Cursor<Tuple, ?> cursor) {
121
122 Tuple tuple = cursor.getKey();
123 int o = tuple.get(0);
124
125 if (isUnique(hash2Amount, previous, o)) {
126 next.ensureSize(o);
127 } else {
128 Object value = cursor.getValue();
129 long tupleHash = getTupleHash1(tuple, value, previous);
130
131 addHash(next, o, impactValues[0], tupleHash);
132 }
133 }
134
135 private void lazyImpactCalculation2(LongIntMap hash2Amount, ObjectCodeImpl previous, ObjectCodeImpl next,
136 long[] impactValues, Cursor<Tuple, ?> cursor) {
137 final Tuple tuple = cursor.getKey();
138 final int o1 = tuple.get(0);
139 final int o2 = tuple.get(1);
140
141 final boolean u1 = isUnique(hash2Amount, previous, o1);
142 final boolean u2 = isUnique(hash2Amount, previous, o2);
143
144 if (u1 && u2) {
145 next.ensureSize(o1);
146 next.ensureSize(o2);
147 } else {
148 Object value = cursor.getValue();
149 long tupleHash = getTupleHash2(tuple, value, previous);
150
151 if (!u1) {
152 addHash(next, o1, impactValues[0], tupleHash);
153 next.ensureSize(o2);
154 }
155 if (!u2) {
156 next.ensureSize(o1);
157 addHash(next, o2, impactValues[1], tupleHash);
158 }
159 }
160 }
161
162 private void lazyImpactCalculationN(LongIntMap hash2Amount, ObjectCodeImpl previous, ObjectCodeImpl next,
163 long[] impactValues, Cursor<Tuple, ?> cursor) {
164 final Tuple tuple = cursor.getKey();
165
166 final boolean[] uniques = new boolean[tuple.getSize()];
167 boolean allUnique = true;
168 for (int i = 0; i < tuple.getSize(); i++) {
169 final boolean isUnique = isUnique(hash2Amount, previous, tuple.get(i));
170 uniques[i] = isUnique;
171 allUnique &= isUnique;
172 }
173
174 if (allUnique) {
175 for (int i = 0; i < tuple.getSize(); i++) {
176 next.ensureSize(tuple.get(i));
177 }
178 } else {
179 Object value = cursor.getValue();
180 long tupleHash = getTupleHashN(tuple, value, previous);
181
182 for (int i = 0; i < tuple.getSize(); i++) {
183 int o = tuple.get(i);
184 if (!uniques[i]) {
185 addHash(next, o, impactValues[i], tupleHash);
186 } else {
187 next.ensureSize(o);
188 }
189 }
190 }
191 }
192
193}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java
new file mode 100644
index 00000000..785fda7a
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java
@@ -0,0 +1,115 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.neighbourhood;
7
8import org.eclipse.collections.api.set.primitive.IntSet;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.model.Interpretation;
11import tools.refinery.store.statecoding.ObjectCode;
12import tools.refinery.store.statecoding.StateCodeCalculator;
13import tools.refinery.store.statecoding.StateCoderResult;
14import tools.refinery.store.tuple.Tuple;
15import tools.refinery.store.tuple.Tuple0;
16
17import java.util.List;
18import java.util.Objects;
19
20public class NeighbourhoodCalculator extends AbstractNeighbourhoodCalculator implements StateCodeCalculator {
21 public NeighbourhoodCalculator(List<? extends Interpretation<?>> interpretations, IntSet individuals) {
22 super(interpretations, individuals);
23 }
24
25 public StateCoderResult calculateCodes() {
26 ObjectCodeImpl previousObjectCode = new ObjectCodeImpl();
27 initializeWithIndividuals(previousObjectCode);
28
29 int rounds = 0;
30 do {
31 final ObjectCodeImpl nextObjectCode = rounds == 0 ? new ObjectCodeImpl() :
32 new ObjectCodeImpl(previousObjectCode.getSize());
33
34 constructNextObjectCodes(previousObjectCode, nextObjectCode);
35 previousObjectCode = nextObjectCode;
36 rounds++;
37 } while (rounds <= 7 && rounds <= previousObjectCode.getEffectiveSize());
38
39 long result = calculateLastSum(previousObjectCode);
40 return new StateCoderResult((int) result, previousObjectCode);
41 }
42
43 private long calculateLastSum(ObjectCode codes) {
44 long result = 0;
45 for (var nullImpactValue : nullImpactValues) {
46 result = result * 31 + Objects.hashCode(((Interpretation<?>) nullImpactValue).get(Tuple0.INSTANCE));
47 }
48
49 for (int i = 0; i < codes.getSize(); i++) {
50 final long hash = codes.get(i);
51 result += hash*PRIME;
52 }
53
54 return result;
55 }
56
57 private void constructNextObjectCodes(ObjectCodeImpl previous, ObjectCodeImpl next) {
58 for (var impactValueEntry : this.impactValues.entrySet()) {
59 Interpretation<?> interpretation = (Interpretation<?>) impactValueEntry.getKey();
60 var cursor = interpretation.getAll();
61 int arity = interpretation.getSymbol().arity();
62 long[] impactValue = impactValueEntry.getValue();
63
64 if (arity == 1) {
65 while (cursor.move()) {
66 impactCalculation1(previous, next, impactValue, cursor);
67 }
68 } else if (arity == 2) {
69 while (cursor.move()) {
70 impactCalculation2(previous, next, impactValue, cursor);
71 }
72 } else {
73 while (cursor.move()) {
74 impactCalculationN(previous, next, impactValue, cursor);
75 }
76 }
77 }
78 }
79
80
81 private void impactCalculation1(ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues,
82 Cursor<Tuple, ?> cursor) {
83
84 Tuple tuple = cursor.getKey();
85 int o = tuple.get(0);
86 Object value = cursor.getValue();
87 long tupleHash = getTupleHash1(tuple, value, previous);
88 addHash(next, o, impactValues[0], tupleHash);
89 }
90
91 private void impactCalculation2(ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues,
92 Cursor<Tuple, ?> cursor) {
93 final Tuple tuple = cursor.getKey();
94 final int o1 = tuple.get(0);
95 final int o2 = tuple.get(1);
96
97 Object value = cursor.getValue();
98 long tupleHash = getTupleHash2(tuple, value, previous);
99
100 addHash(next, o1, impactValues[0], tupleHash);
101 addHash(next, o2, impactValues[1], tupleHash);
102 }
103
104 private void impactCalculationN(ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues,
105 Cursor<Tuple, ?> cursor) {
106 final Tuple tuple = cursor.getKey();
107
108 Object value = cursor.getValue();
109 long tupleHash = getTupleHashN(tuple, value, previous);
110
111 for (int i = 0; i < tuple.getSize(); i++) {
112 addHash(next, tuple.get(i), impactValues[i], tupleHash);
113 }
114 }
115}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCodeImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCodeImpl.java
new file mode 100644
index 00000000..0cd7ff58
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCodeImpl.java
@@ -0,0 +1,81 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.neighbourhood;
7
8import tools.refinery.store.statecoding.ObjectCode;
9
10import java.util.Arrays;
11
12public class ObjectCodeImpl implements ObjectCode {
13 private long[] vector;
14 private int size;
15 private int effectiveSize;
16
17 public ObjectCodeImpl() {
18 vector = new long[10];
19 size = 0;
20 effectiveSize = 0;
21 }
22
23 public ObjectCodeImpl(int size) {
24 this.vector = new long[size];
25 this.size = size;
26 effectiveSize = 0;
27 }
28
29 public ObjectCodeImpl(ObjectCodeImpl copy) {
30 this.vector = Arrays.copyOf(copy.vector, copy.size);
31 this.size = copy.size;
32 effectiveSize = copy.effectiveSize;
33 }
34
35 public void ensureSize(int object) {
36 if (object >= size) {
37 size = object + 1;
38 }
39
40 if (object >= vector.length) {
41 int newLength = vector.length * 2;
42 while (object >= newLength) {
43 newLength *= 2;
44 }
45
46 long[] newVector = new long[newLength];
47 System.arraycopy(vector, 0, newVector, 0, vector.length);
48 this.vector = newVector;
49 }
50 }
51
52 public long get(int object) {
53 if (object < vector.length) {
54 return vector[object];
55 } else {
56 return 0;
57 }
58 }
59
60 public void set(int object, long value) {
61 ensureSize(object);
62 final long valueToPut = value == 0 ? 1 : value;
63 if (vector[object] == 0) effectiveSize++;
64 vector[object] = valueToPut;
65 }
66
67 public int getSize() {
68 return this.size;
69 }
70
71 public int getEffectiveSize() {
72 return this.effectiveSize;
73 }
74
75 @Override
76 public String toString() {
77 return "ObjectCodeImpl{" +
78 "vector=" + Arrays.toString(Arrays.copyOf(vector, this.size)) +
79 '}';
80 }
81}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairing.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairing.java
new file mode 100644
index 00000000..0682e1a4
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairing.java
@@ -0,0 +1,89 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.stateequivalence;
7
8import org.eclipse.collections.api.factory.primitive.IntIntMaps;
9import org.eclipse.collections.api.map.primitive.IntIntMap;
10import org.eclipse.collections.api.set.primitive.IntSet;
11
12import java.util.*;
13
14public class CombinationNodePairing implements NodePairing {
15 private final int[] left;
16 private final int[] right;
17
18 CombinationNodePairing(IntSet left, IntSet right) {
19 this.left = left.toArray();
20 this.right = right.toArray();
21
22 Arrays.sort(this.left);
23 Arrays.sort(this.right);
24 }
25
26 @Override
27 public int size() {
28 return left.length;
29 }
30
31 private static final int LIMIT = 5;
32 // Enum-based singleton used to delay generating all permutations until they are first needed.
33 @SuppressWarnings("squid:S6548")
34 private enum PermutationsHolder {
35 INSTANCE;
36
37 final CombinationNodePairingPermutations permutations = new CombinationNodePairingPermutations(LIMIT);
38 }
39
40 @Override
41 public List<IntIntMap> permutations() {
42 int limit = this.size();
43 Iterable<Integer> interval = () -> new IntervalIterator(limit);
44
45 if (isComplete()) {
46 final List<int[]> p = PermutationsHolder.INSTANCE.permutations.getPermutations(this.size() - 1);
47 return p.stream().map(x -> constructPermutationMap(interval, x)).toList();
48 } else {
49 return List.of(constructTrivialMap(interval));
50 }
51 }
52
53 private IntIntMap constructTrivialMap(Iterable<Integer> interval) {
54 return IntIntMaps.immutable.from(interval, l -> left[l], r -> right[r]);
55 }
56
57 private IntIntMap constructPermutationMap(Iterable<Integer> interval, int[] permutation) {
58 return IntIntMaps.immutable.from(interval, l -> left[l], r -> right[permutation[r]]);
59 }
60
61 @Override
62 public boolean isComplete() {
63 return this.size() <= LIMIT;
64 }
65
66 private static class IntervalIterator implements Iterator<Integer> {
67 private final int limit;
68 private int value = 0;
69
70 private IntervalIterator(int max) {
71 this.limit = max;
72 }
73
74 @Override
75 public boolean hasNext() {
76 return value < limit;
77 }
78
79 @Override
80 public Integer next() {
81 if (value >= limit) {
82 throw new NoSuchElementException("End of interval");
83 }
84 int next = value;
85 value++;
86 return next;
87 }
88 }
89}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairingPermutations.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairingPermutations.java
new file mode 100644
index 00000000..eacd3a2a
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairingPermutations.java
@@ -0,0 +1,57 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.stateequivalence;
7
8import java.util.ArrayList;
9import java.util.List;
10
11class CombinationNodePairingPermutations {
12 private final List<List<int[]>> permutations = new ArrayList<>();
13
14 public CombinationNodePairingPermutations(int max) {
15 initializePermutations(max);
16 }
17
18 public List<int[]> getPermutations(int max) {
19 if (max >= permutations.size()) {
20 throw new IllegalArgumentException("Only permutations up to %d elements are supported".formatted(max));
21 }
22 return permutations.get(max);
23 }
24
25 /**
26 * Generates and stores permutations up to a given size. If the number would be more than a limit, it provides a
27 * single permutation only.
28 *
29 * @param max The max number in the permutation
30 * @return A complete list of permutations of numbers 0...max, or a single permutation.
31 */
32 private List<int[]> initializePermutations(int max) {
33 if (max < permutations.size()) {
34 return permutations.get(max);
35 }
36 if (max == 0) {
37 List<int[]> result = new ArrayList<>();
38 result.add(new int[1]);
39 permutations.add(result);
40 return result;
41 }
42 List<int[]> result = new ArrayList<>();
43 List<int[]> previousPermutations = initializePermutations(max - 1);
44 for (var permutation : previousPermutations) {
45 for (int pos = 0; pos <= max; pos++) {
46 int[] newPermutation = new int[max + 1];
47 System.arraycopy(permutation, 0, newPermutation, 0, pos);
48 newPermutation[pos] = max;
49 if (max - (pos + 1) >= 0)
50 System.arraycopy(permutation, pos + 1, newPermutation, pos + 1 + 1, max - (pos + 1));
51 result.add(newPermutation);
52 }
53 }
54 permutations.add(result);
55 return result;
56 }
57}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/NodePairing.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/NodePairing.java
new file mode 100644
index 00000000..f45f0d2e
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/NodePairing.java
@@ -0,0 +1,33 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.stateequivalence;
7
8import org.eclipse.collections.api.map.primitive.IntIntMap;
9import org.eclipse.collections.api.set.primitive.IntSet;
10
11import java.util.List;
12
13public interface NodePairing {
14 int size();
15
16 List<IntIntMap> permutations();
17
18 boolean isComplete();
19
20 static NodePairing constructNodePairing(IntSet left, IntSet right){
21 if(left.size() != right.size()) {
22 return null;
23 }
24
25 if(left.size() == 1) {
26 int leftValue = left.intIterator().next();
27 int rightValue = right.intIterator().next();
28 return new TrivialNodePairing(leftValue, rightValue);
29 } else {
30 return new CombinationNodePairing(left,right);
31 }
32 }
33}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/PermutationMorphism.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/PermutationMorphism.java
new file mode 100644
index 00000000..bc4d723a
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/PermutationMorphism.java
@@ -0,0 +1,64 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.stateequivalence;
7
8import org.eclipse.collections.api.map.primitive.IntIntMap;
9import tools.refinery.store.statecoding.Morphism;
10
11import java.util.List;
12
13public class PermutationMorphism implements Morphism {
14 private final IntIntMap object2PermutationGroup;
15 private final List<? extends List<? extends IntIntMap>> permutationsGroups;
16 private final int[] selection;
17 private boolean hasNext;
18
19 PermutationMorphism(IntIntMap object2PermutationGroup,
20 List<? extends List<? extends IntIntMap>> permutationsGroups) {
21 this.object2PermutationGroup = object2PermutationGroup;
22 this.permutationsGroups = permutationsGroups;
23
24 this.selection = new int[this.permutationsGroups.size()];
25 this.hasNext = true;
26 }
27
28 public boolean next() {
29 return next(0);
30 }
31
32 private boolean next(int position) {
33 if (position >= permutationsGroups.size()) {
34 this.hasNext = false;
35 return false;
36 }
37 if (selection[position] + 1 < permutationsGroups.get(position).size()) {
38 selection[position] = selection[position] + 1;
39 return true;
40 } else {
41 selection[position] = 0;
42 return next(position + 1);
43 }
44 }
45
46 @Override
47 public int get(int object) {
48 if(!hasNext) {
49 throw new IllegalArgumentException("No next permutation!");
50 }
51
52 final int groupIndex = object2PermutationGroup.get(object);
53 final var selectedGroup = permutationsGroups.get(groupIndex);
54 final int permutationIndex = selection[groupIndex];
55 final var selectedPermutation = selectedGroup.get(permutationIndex);
56
57 return selectedPermutation.get(object);
58 }
59
60 @Override
61 public int getSize() {
62 return object2PermutationGroup.size();
63 }
64}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java
new file mode 100644
index 00000000..5a62d8a0
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java
@@ -0,0 +1,166 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.stateequivalence;
7
8import org.eclipse.collections.api.factory.primitive.IntIntMaps;
9import org.eclipse.collections.api.factory.primitive.IntSets;
10import org.eclipse.collections.api.factory.primitive.LongObjectMaps;
11import org.eclipse.collections.api.map.primitive.IntIntMap;
12import org.eclipse.collections.api.map.primitive.MutableIntIntMap;
13import org.eclipse.collections.api.map.primitive.MutableLongObjectMap;
14import org.eclipse.collections.api.set.primitive.IntSet;
15import org.eclipse.collections.api.set.primitive.MutableIntSet;
16import tools.refinery.store.model.AnyInterpretation;
17import tools.refinery.store.model.Interpretation;
18import tools.refinery.store.statecoding.Morphism;
19import tools.refinery.store.statecoding.ObjectCode;
20import tools.refinery.store.statecoding.StateEquivalenceChecker;
21import tools.refinery.store.tuple.Tuple;
22
23import java.util.ArrayList;
24import java.util.List;
25import java.util.Objects;
26
27public class StateEquivalenceCheckerImpl implements StateEquivalenceChecker {
28 public static final int LIMIT = 1000;
29
30 @Override
31 public EquivalenceResult constructMorphism(IntSet individuals,
32 List<? extends AnyInterpretation> interpretations1,
33 ObjectCode code1,
34 List<? extends AnyInterpretation> interpretations2,
35 ObjectCode code2) {
36 MutableIntIntMap object2PermutationGroup = IntIntMaps.mutable.empty();
37 List<List<IntIntMap>> permutationsGroups = new ArrayList<>();
38
39 final EquivalenceResult permutations = constructPermutationNavigation(individuals,
40 indexByHash(code1, individuals), indexByHash(code2, individuals),
41 object2PermutationGroup, permutationsGroups);
42
43 if (permutations == EquivalenceResult.DIFFERENT) {
44 return EquivalenceResult.DIFFERENT;
45 }
46
47 boolean hasNext;
48 PermutationMorphism morphism = new PermutationMorphism(object2PermutationGroup, permutationsGroups);
49 int tried = 0;
50 do {
51 if (testMorphism(interpretations1, interpretations2, morphism)) {
52 return permutations;
53 }
54
55 if (tried >= LIMIT) {
56 return EquivalenceResult.UNKNOWN;
57 }
58
59 hasNext = morphism.next();
60 tried++;
61 } while (hasNext);
62
63 if (permutations == EquivalenceResult.UNKNOWN) {
64 return EquivalenceResult.UNKNOWN;
65 } else {
66 return EquivalenceResult.DIFFERENT;
67 }
68 }
69
70 private MutableLongObjectMap<MutableIntSet> indexByHash(ObjectCode code, IntSet individuals) {
71 MutableLongObjectMap<MutableIntSet> result = LongObjectMaps.mutable.empty();
72 for (int o = 0; o < code.getSize(); o++) {
73 if (!individuals.contains(o)) {
74 long hash = code.get(o);
75 if (hash != 0) {
76 var equivalenceClass = result.get(hash);
77 if (equivalenceClass == null) {
78 equivalenceClass = IntSets.mutable.empty();
79 result.put(hash, equivalenceClass);
80 }
81 equivalenceClass.add(o);
82 }
83 }
84 }
85 return result;
86 }
87
88 private EquivalenceResult constructPermutationNavigation(
89 IntSet individuals, MutableLongObjectMap<MutableIntSet> map1, MutableLongObjectMap<MutableIntSet> map2,
90 MutableIntIntMap object2OptionIndex, List<List<IntIntMap>> listOfOptions) {
91 if (map1.size() != map2.size()) {
92 return EquivalenceResult.DIFFERENT;
93 }
94
95 var iterator = map1.keySet().longIterator();
96
97 boolean allComplete = true;
98
99 while (iterator.hasNext()) {
100 long hash = iterator.next();
101 var set1 = map1.get(hash);
102 var set2 = map2.get(hash);
103 if (set2 == null) {
104 return EquivalenceResult.DIFFERENT;
105 }
106
107 var pairing = NodePairing.constructNodePairing(set1, set2);
108 if (pairing == null) {
109 return EquivalenceResult.DIFFERENT;
110 }
111
112 allComplete &= pairing.isComplete();
113
114 final int optionIndex = listOfOptions.size();
115 set1.forEach(key -> object2OptionIndex.put(key, optionIndex));
116 listOfOptions.add(pairing.permutations());
117 }
118
119 individuals.forEach(o -> listOfOptions.add(o, List.of(IntIntMaps.immutable.of(o, o))));
120
121 if (allComplete) {
122 return EquivalenceResult.ISOMORPHIC;
123 } else {
124 return EquivalenceResult.UNKNOWN;
125 }
126 }
127
128 private boolean testMorphism(List<? extends AnyInterpretation> s, List<? extends AnyInterpretation> t,
129 Morphism m) {
130 for (int interpretationIndex = 0; interpretationIndex < s.size(); interpretationIndex++) {
131 var sI = s.get(interpretationIndex);
132 var tI = t.get(interpretationIndex);
133
134 var cursor = ((Interpretation<?>) sI).getAll();
135 while (cursor.move()) {
136 final Tuple sTuple = cursor.getKey();
137 final Object sValue = cursor.getValue();
138
139 final Tuple tTuple = apply(sTuple, m);
140 final Object tValue = ((Interpretation<?>) tI).get(tTuple);
141
142 if (!Objects.equals(sValue, tValue)) {
143 return false;
144 }
145 }
146 }
147 return true;
148 }
149
150 private Tuple apply(Tuple t, Morphism m) {
151 final int arity = t.getSize();
152 if (arity == 0) {
153 return Tuple.of();
154 } else if (arity == 1) {
155 return Tuple.of(m.get(t.get(0)));
156 } else if (arity == 2) {
157 return Tuple.of(m.get(t.get(0)), m.get(t.get(1)));
158 } else {
159 int[] newTupleIndices = new int[arity];
160 for (int i = 0; i < arity; i++) {
161 newTupleIndices[i] = m.get(t.get(i));
162 }
163 return Tuple.of(newTupleIndices);
164 }
165 }
166}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/TrivialNodePairing.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/TrivialNodePairing.java
new file mode 100644
index 00000000..f5eadfb9
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/TrivialNodePairing.java
@@ -0,0 +1,36 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.stateequivalence;
7
8import org.eclipse.collections.api.factory.primitive.IntIntMaps;
9import org.eclipse.collections.api.map.primitive.IntIntMap;
10
11import java.util.List;
12
13public class TrivialNodePairing implements NodePairing {
14 final int left;
15 final int right;
16
17 TrivialNodePairing(int left, int right) {
18 this.left = left;
19 this.right = right;
20 }
21
22 @Override
23 public int size() {
24 return 1;
25 }
26
27 @Override
28 public List<IntIntMap> permutations() {
29 return List.of(IntIntMaps.immutable.of(left,right));
30 }
31
32 @Override
33 public boolean isComplete() {
34 return true;
35 }
36}
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/InOrderCursorTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/InOrderCursorTest.java
new file mode 100644
index 00000000..98756460
--- /dev/null
+++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/InOrderCursorTest.java
@@ -0,0 +1,56 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.tests;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.map.VersionedMapStore;
10import tools.refinery.store.map.VersionedMapStoreFactoryBuilder;
11import tools.refinery.store.map.internal.state.InOrderMapCursor;
12import tools.refinery.store.map.internal.state.VersionedMapStateImpl;
13import tools.refinery.store.map.tests.utils.MapTestEnvironment;
14
15import static org.junit.jupiter.api.Assertions.*;
16
17class InOrderCursorTest {
18 @Test
19 void testCursor() {
20 var store = VersionedMapStore.<Integer,String>builder()
21 .strategy(VersionedMapStoreFactoryBuilder.StoreStrategy.STATE)
22 .stateBasedImmutableWhenCommitting(true)
23 .stateBasedHashProvider(MapTestEnvironment.prepareHashProvider(false))
24 .stateBasedSharingStrategy(VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE)
25 .defaultValue("x")
26 .build()
27 .createOne();
28
29 VersionedMapStateImpl<Integer,String> map = (VersionedMapStateImpl<Integer,String>) store.createMap();
30 checkMove(map,0);
31
32 map.put(1,"A");
33 map.commit();
34 checkMove(map,1);
35
36
37 map.put(2,"B");
38 map.commit();
39 checkMove(map,2);
40
41 map.put(3,"C");
42 map.commit();
43 checkMove(map,3);
44
45 }
46
47 private void checkMove(VersionedMapStateImpl<Integer,String> map, int num) {
48 InOrderMapCursor<Integer,String> cursor = new InOrderMapCursor<>(map);
49 for(int i=0; i<num; i++) {
50 assertTrue(cursor.move());
51 assertFalse(cursor.isTerminated());
52 }
53 assertFalse(cursor.move());
54 assertTrue(cursor.isTerminated());
55 }
56}
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/MapUnitTests.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/MapUnitTests.java
index 153f2e78..cc2e425c 100644
--- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/MapUnitTests.java
+++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/MapUnitTests.java
@@ -7,7 +7,7 @@ package tools.refinery.store.map.tests;
7 7
8import org.junit.jupiter.api.Test; 8import org.junit.jupiter.api.Test;
9import tools.refinery.store.map.VersionedMapStore; 9import tools.refinery.store.map.VersionedMapStore;
10import tools.refinery.store.map.VersionedMapStoreImpl; 10import tools.refinery.store.map.internal.state.VersionedMapStoreStateImpl;
11import tools.refinery.store.model.TupleHashProvider; 11import tools.refinery.store.model.TupleHashProvider;
12import tools.refinery.store.tuple.Tuple; 12import tools.refinery.store.tuple.Tuple;
13 13
@@ -16,11 +16,82 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
16class MapUnitTests { 16class MapUnitTests {
17 @Test 17 @Test
18 void defaultTest() { 18 void defaultTest() {
19 VersionedMapStore<Tuple, Boolean> store = new VersionedMapStoreImpl<>(TupleHashProvider.INSTANCE, false); 19 VersionedMapStore<Tuple, Boolean> store = new VersionedMapStoreStateImpl<>(TupleHashProvider.INSTANCE, false);
20 var map = store.createMap(); 20 var map = store.createMap();
21 var out1 = map.put(Tuple.of(0), true); 21 var out1 = map.put(Tuple.of(0), true);
22 assertEquals(false, out1); 22 assertEquals(false, out1);
23 var out2 = map.put(Tuple.of(1), true); 23 var out2 = map.put(Tuple.of(1), true);
24 assertEquals(false, out2); 24 assertEquals(false, out2);
25 } 25 }
26
27 @Test
28 void deltaRestoreTest() {
29 VersionedMapStore<Integer,String> store =
30 VersionedMapStore.<Integer,String>builder().defaultValue("x").build().createOne();
31 var map = store.createMap();
32 map.put(1,"val");
33 var version1 = map.commit();
34 map.put(1,"x");
35 map.restore(version1);
36 System.out.println(map.getSize());
37 assertEquals(1,map.getSize());
38 }
39
40 @Test
41 void deltaRestoreTest2() {
42 VersionedMapStore<Integer,String> store =
43 VersionedMapStore.<Integer,String>builder().defaultValue("x").build().createOne();
44 var map = store.createMap();
45 map.put(1,"x");
46 var version1 = map.commit();
47 map.put(1,"1");
48 map.restore(version1);
49 System.out.println(map.getSize());
50 assertEquals(0,map.getSize());
51 }
52 @Test
53 void deltaRestoreTest3() {
54 VersionedMapStore<Integer,String> store =
55 VersionedMapStore.<Integer,String>builder().defaultValue("x").build().createOne();
56 var map = store.createMap();
57 map.commit();
58 map.put(1,"1");
59 map.put(2,"x");
60 assertEquals(1,map.getSize());
61 var version1 = map.commit();
62 map.put(1,"x");
63 assertEquals(0,map.getSize());
64 map.put(2,"2");
65 assertEquals(1,map.getSize());
66 map.put(2,"x");
67 assertEquals(0,map.getSize());
68 var version2 = map.commit();
69 map.restore(version1);
70 assertEquals(1,map.getSize());
71 map.restore(version2);
72 assertEquals(0,map.getSize());
73 }
74
75 @Test
76 void deltaRestoreTest4() {
77 VersionedMapStore<Integer,String> store =
78 VersionedMapStore.<Integer,String>builder().defaultValue("x").build().createOne();
79 var map = store.createMap();
80 map.commit();
81 map.put(1,"1");
82 map.put(2,"x");
83 assertEquals(1,map.getSize());
84 var version1 = map.commit();
85 map.put(1,"x");
86 assertEquals(0,map.getSize());
87 map.put(2,"2");
88 assertEquals(1,map.getSize());
89 map.put(2,"x");
90 assertEquals(0,map.getSize());
91 var version2 = map.commit();
92 map.restore(version1);
93 assertEquals(1,map.getSize());
94 map.restore(version2);
95 assertEquals(0,map.getSize());
96 }
26} 97}
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java
index eabe5bd1..58206eda 100644
--- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java
+++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java
@@ -5,33 +5,32 @@
5 */ 5 */
6package tools.refinery.store.map.tests.fuzz; 6package tools.refinery.store.map.tests.fuzz;
7 7
8import static org.junit.jupiter.api.Assertions.fail;
9
10import java.util.Random;
11import java.util.stream.Stream;
12
13import org.junit.jupiter.api.Tag; 8import org.junit.jupiter.api.Tag;
14import org.junit.jupiter.api.Timeout; 9import org.junit.jupiter.api.Timeout;
15import org.junit.jupiter.params.ParameterizedTest; 10import org.junit.jupiter.params.ParameterizedTest;
16import org.junit.jupiter.params.provider.Arguments; 11import org.junit.jupiter.params.provider.Arguments;
17import org.junit.jupiter.params.provider.MethodSource; 12import org.junit.jupiter.params.provider.MethodSource;
18
19import tools.refinery.store.map.ContinousHashProvider;
20import tools.refinery.store.map.VersionedMapStore; 13import tools.refinery.store.map.VersionedMapStore;
21import tools.refinery.store.map.VersionedMapStoreImpl; 14import tools.refinery.store.map.VersionedMapStoreFactoryBuilder;
22import tools.refinery.store.map.internal.VersionedMapImpl;
23import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; 15import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils;
24import tools.refinery.store.map.tests.utils.MapTestEnvironment; 16import tools.refinery.store.map.tests.utils.MapTestEnvironment;
25 17
18import java.util.Random;
19import java.util.stream.Stream;
20
21import static org.junit.jupiter.api.Assertions.fail;
22import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*;
23
26class CommitFuzzTest { 24class CommitFuzzTest {
27 private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency,
28 boolean evilHash) {
29 String[] values = MapTestEnvironment.prepareValues(maxValue);
30 ContinousHashProvider<Integer> chp = MapTestEnvironment.prepareHashProvider(evilHash);
31 25
32 VersionedMapStore<Integer, String> store = new VersionedMapStoreImpl<Integer, String>(chp, values[0]); 26 private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue,
33 VersionedMapImpl<Integer, String> sut = (VersionedMapImpl<Integer, String>) store.createMap(); 27 boolean nullDefault, int commitFrequency,
34 MapTestEnvironment<Integer, String> e = new MapTestEnvironment<Integer, String>(sut); 28 VersionedMapStoreFactoryBuilder<Integer, String> builder) {
29 String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault);
30
31 VersionedMapStore<Integer, String> store = builder.defaultValue(values[0]).build().createOne();
32 var sut = store.createMap();
33 MapTestEnvironment<Integer, String> e = new MapTestEnvironment<>(sut);
35 34
36 Random r = new Random(seed); 35 Random r = new Random(seed);
37 36
@@ -39,24 +38,13 @@ class CommitFuzzTest {
39 } 38 }
40 39
41 private void iterativeRandomPutsAndCommits(String scenario, int steps, int maxKey, String[] values, 40 private void iterativeRandomPutsAndCommits(String scenario, int steps, int maxKey, String[] values,
42 MapTestEnvironment<Integer, String> e, Random r, int commitFrequency) { 41 MapTestEnvironment<Integer, String> e, Random r, int commitFrequency) {
43 int stopAt = -1;
44 for (int i = 0; i < steps; i++) { 42 for (int i = 0; i < steps; i++) {
45 int index = i + 1; 43 int index = i + 1;
46 int nextKey = r.nextInt(maxKey); 44 int nextKey = r.nextInt(maxKey);
47 String nextValue = values[r.nextInt(values.length)]; 45 String nextValue = values[r.nextInt(values.length)];
48 if (index == stopAt) {
49 System.out.println("issue!");
50 System.out.println("State before:");
51 e.printComparison();
52 e.sut.prettyPrint();
53 System.out.println("Next: put(" + nextKey + "," + nextValue + ")");
54 }
55 try { 46 try {
56 e.put(nextKey, nextValue); 47 e.put(nextKey, nextValue);
57 if (index == stopAt) {
58 e.sut.prettyPrint();
59 }
60 e.checkEquivalence(scenario + ":" + index); 48 e.checkEquivalence(scenario + ":" + index);
61 } catch (Exception exception) { 49 } catch (Exception exception) {
62 exception.printStackTrace(); 50 exception.printStackTrace();
@@ -64,35 +52,37 @@ class CommitFuzzTest {
64 } 52 }
65 MapTestEnvironment.printStatus(scenario, index, steps, null); 53 MapTestEnvironment.printStatus(scenario, index, steps, null);
66 if (index % commitFrequency == 0) { 54 if (index % commitFrequency == 0) {
67 e.sut.commit(); 55 e.commit();
68 } 56 }
69 } 57 }
70 } 58 }
71 59
72 @ParameterizedTest(name = "Commit {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") 60 public static final String title = "Commit {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit frequency={5} " +
61 "seed={6} config={7}";
62
63 @ParameterizedTest(name = title)
73 @MethodSource 64 @MethodSource
74 @Timeout(value = 10) 65 @Timeout(value = 10)
75 @Tag("fuzz") 66 @Tag("fuzz")
76 void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, 67 void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency,
77 boolean evilHash) { 68 int seed, VersionedMapStoreFactoryBuilder<Integer, String> builder) {
78 runFuzzTest("CommitS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, 69 runFuzzTest("CommitS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues,
79 commitFrequency, evilHash); 70 nullDefault, commitFrequency, builder);
80 } 71 }
81 72
82 static Stream<Arguments> parametrizedFastFuzz() { 73 static Stream<Arguments> parametrizedFastFuzz() {
83 return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, 74 return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions,
84 new Object[] { 2, 3 }, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, 75 commitFrequencyOptions, randomSeedOptions, storeConfigs);
85 new Object[] { false, true });
86 } 76 }
87 77
88 @ParameterizedTest(name = "Commit {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") 78 @ParameterizedTest(name = title)
89 @MethodSource 79 @MethodSource
90 @Tag("fuzz") 80 @Tag("fuzz")
91 @Tag("slow") 81 @Tag("slow")
92 void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, 82 void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency,
93 boolean evilHash) { 83 int seed, VersionedMapStoreFactoryBuilder<Integer, String> builder) {
94 runFuzzTest("CommitS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, 84 runFuzzTest("CommitS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues,
95 commitFrequency, evilHash); 85 nullDefault, commitFrequency, builder);
96 } 86 }
97 87
98 static Stream<Arguments> parametrizedSlowFuzz() { 88 static Stream<Arguments> parametrizedSlowFuzz() {
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java
index b0502a2b..c49911b8 100644
--- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java
+++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java
@@ -10,8 +10,10 @@ import org.junit.jupiter.api.Timeout;
10import org.junit.jupiter.params.ParameterizedTest; 10import org.junit.jupiter.params.ParameterizedTest;
11import org.junit.jupiter.params.provider.Arguments; 11import org.junit.jupiter.params.provider.Arguments;
12import org.junit.jupiter.params.provider.MethodSource; 12import org.junit.jupiter.params.provider.MethodSource;
13import tools.refinery.store.map.*; 13import tools.refinery.store.map.Cursor;
14import tools.refinery.store.map.internal.VersionedMapImpl; 14import tools.refinery.store.map.VersionedMap;
15import tools.refinery.store.map.VersionedMapStore;
16import tools.refinery.store.map.VersionedMapStoreFactoryBuilder;
15import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; 17import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils;
16import tools.refinery.store.map.tests.utils.MapTestEnvironment; 18import tools.refinery.store.map.tests.utils.MapTestEnvironment;
17 19
@@ -22,23 +24,25 @@ import java.util.List;
22import java.util.Random; 24import java.util.Random;
23import java.util.stream.Stream; 25import java.util.stream.Stream;
24 26
25import static org.junit.jupiter.api.Assertions.*; 27import static org.junit.jupiter.api.Assertions.fail;
28import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*;
26 29
27class ContentEqualsFuzzTest { 30class ContentEqualsFuzzTest {
28 private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency, 31 private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue,
29 boolean evilHash) { 32 boolean nullDefault, int commitFrequency,
30 String[] values = MapTestEnvironment.prepareValues(maxValue); 33 VersionedMapStoreFactoryBuilder<Integer, String> builder) {
31 ContinousHashProvider<Integer> chp = MapTestEnvironment.prepareHashProvider(evilHash); 34 String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault);
35
32 36
33 Random r = new Random(seed); 37 Random r = new Random(seed);
34 38
35 iterativeRandomPutsAndCommitsThenCompare(scenario, chp, steps, maxKey, values, r, commitFrequency); 39 iterativeRandomPutsAndCommitsThenCompare(scenario, builder, steps, maxKey, values, r, commitFrequency);
36 } 40 }
37 41
38 private void iterativeRandomPutsAndCommitsThenCompare(String scenario, ContinousHashProvider<Integer> chp, 42 private void iterativeRandomPutsAndCommitsThenCompare(String scenario, VersionedMapStoreFactoryBuilder<Integer, String> builder,
39 int steps, int maxKey, String[] values, Random r, 43 int steps, int maxKey, String[] values, Random r,
40 int commitFrequency) { 44 int commitFrequency) {
41 VersionedMapStore<Integer, String> store1 = new VersionedMapStoreImpl<Integer, String>(chp, values[0]); 45 VersionedMapStore<Integer, String> store1 = builder.defaultValue(values[0]).build().createOne();
42 VersionedMap<Integer, String> sut1 = store1.createMap(); 46 VersionedMap<Integer, String> sut1 = store1.createMap();
43 47
44 // Fill one map 48 // Fill one map
@@ -68,7 +72,7 @@ class ContentEqualsFuzzTest {
68 // Randomize the order of the content 72 // Randomize the order of the content
69 Collections.shuffle(content, r); 73 Collections.shuffle(content, r);
70 74
71 VersionedMapStore<Integer, String> store2 = new VersionedMapStoreImpl<Integer, String>(chp, values[0]); 75 VersionedMapStore<Integer, String> store2 = builder.defaultValue(values[0]).build().createOne();
72 VersionedMap<Integer, String> sut2 = store2.createMap(); 76 VersionedMap<Integer, String> sut2 = store2.createMap();
73 int index2 = 1; 77 int index2 = 1;
74 for (SimpleEntry<Integer, String> entry : content) { 78 for (SimpleEntry<Integer, String> entry : content) {
@@ -78,40 +82,39 @@ class ContentEqualsFuzzTest {
78 } 82 }
79 83
80 // Check the integrity of the maps 84 // Check the integrity of the maps
81 ((VersionedMapImpl<Integer, String>) sut1).checkIntegrity(); 85 sut1.checkIntegrity();
82 ((VersionedMapImpl<Integer, String>) sut2).checkIntegrity(); 86 sut2.checkIntegrity();
83 87
84 // Compare the two maps 88 // Compare the two maps
85 MapTestEnvironment.compareTwoMaps(scenario, sut1, sut2); 89 MapTestEnvironment.compareTwoMaps(scenario, sut1, sut2);
86 } 90 }
87 91
88 @ParameterizedTest(name = "Compare {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} " + 92 public static final String title = "Compare {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} commit frequency={5}" +
89 "evil-hash={6}") 93 "seed={6} config={7}";
94
95 @ParameterizedTest(name = title)
90 @MethodSource 96 @MethodSource
91 @Timeout(value = 10) 97 @Timeout(value = 10)
92 @Tag("fuzz") 98 @Tag("fuzz")
93 void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, 99 void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency,
94 boolean evilHash) { 100 int seed, VersionedMapStoreFactoryBuilder<Integer, String> builder) {
95 runFuzzTest("CompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, 101 runFuzzTest("CompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues,
96 commitFrequency, evilHash); 102 nullDefault, commitFrequency, builder);
97 } 103 }
98 104
99 static Stream<Arguments> parametrizedFastFuzz() { 105 static Stream<Arguments> parametrizedFastFuzz() {
100 return FuzzTestUtils.permutationWithSize(new Object[]{FuzzTestUtils.FAST_STEP_COUNT}, new Object[]{3, 32, 106 return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions,
101 32 * 32}, 107 commitFrequencyOptions, randomSeedOptions, storeConfigs);
102 new Object[]{2, 3}, new Object[]{1, 10, 100}, new Object[]{1, 2, 3},
103 new Object[]{false, true});
104 } 108 }
105 109
106 @ParameterizedTest(name = "Compare {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} " + 110 @ParameterizedTest(name = title)
107 "evil-hash={6}")
108 @MethodSource 111 @MethodSource
109 @Tag("fuzz") 112 @Tag("fuzz")
110 @Tag("slow") 113 @Tag("slow")
111 void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, 114 void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean defaultNull, int commitFrequency,
112 boolean evilHash) { 115 int seed, VersionedMapStoreFactoryBuilder<Integer, String> builder) {
113 runFuzzTest("CompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, 116 runFuzzTest("CompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues,
114 commitFrequency, evilHash); 117 defaultNull, commitFrequency, builder);
115 } 118 }
116 119
117 static Stream<Arguments> parametrizedSlowFuzz() { 120 static Stream<Arguments> parametrizedSlowFuzz() {
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java
index 8274336e..94259edc 100644
--- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java
+++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java
@@ -5,115 +5,134 @@
5 */ 5 */
6package tools.refinery.store.map.tests.fuzz; 6package tools.refinery.store.map.tests.fuzz;
7 7
8import static org.junit.jupiter.api.Assertions.fail;
9
10import java.util.Random;
11import java.util.stream.Stream;
12
13import org.junit.jupiter.api.Tag; 8import org.junit.jupiter.api.Tag;
14import org.junit.jupiter.api.Timeout; 9import org.junit.jupiter.api.Timeout;
15import org.junit.jupiter.params.ParameterizedTest; 10import org.junit.jupiter.params.ParameterizedTest;
16import org.junit.jupiter.params.provider.Arguments; 11import org.junit.jupiter.params.provider.Arguments;
17import org.junit.jupiter.params.provider.MethodSource; 12import org.junit.jupiter.params.provider.MethodSource;
18 13import tools.refinery.store.map.*;
19import tools.refinery.store.map.ContinousHashProvider;
20import tools.refinery.store.map.DiffCursor;
21import tools.refinery.store.map.VersionedMapStore;
22import tools.refinery.store.map.VersionedMapStoreImpl;
23import tools.refinery.store.map.internal.VersionedMapImpl;
24import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; 14import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils;
25import tools.refinery.store.map.tests.utils.MapTestEnvironment; 15import tools.refinery.store.map.tests.utils.MapTestEnvironment;
26 16
17import java.util.HashMap;
18import java.util.Map;
19import java.util.Random;
20import java.util.stream.Stream;
21
22import static org.junit.jupiter.api.Assertions.fail;
23import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*;
24
27class DiffCursorFuzzTest { 25class DiffCursorFuzzTest {
28 private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency, 26 private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault,
29 boolean evilHash) { 27 int commitFrequency, boolean commitBeforeDiffCursor,
30 String[] values = MapTestEnvironment.prepareValues(maxValue); 28 VersionedMapStoreFactoryBuilder<Integer, String> builder) {
31 ContinousHashProvider<Integer> chp = MapTestEnvironment.prepareHashProvider(evilHash); 29 String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault);
32 30
33 VersionedMapStore<Integer, String> store = new VersionedMapStoreImpl<Integer, String>(chp, values[0]); 31 VersionedMapStore<Integer, String> store = builder.defaultValue(values[0]).build().createOne();
34 iterativeRandomPutsAndCommitsThenDiffcursor(scenario, store, steps, maxKey, values, seed, commitFrequency); 32 iterativeRandomPutsAndCommitsThenDiffCursor(scenario, store, steps, maxKey, values, seed, commitFrequency,
33 commitBeforeDiffCursor);
35 } 34 }
36 35
37 private void iterativeRandomPutsAndCommitsThenDiffcursor(String scenario, VersionedMapStore<Integer, String> store, 36 private void iterativeRandomPutsAndCommitsThenDiffCursor(String scenario, VersionedMapStore<Integer, String> store,
38 int steps, int maxKey, String[] values, int seed, int commitFrequency) { 37 int steps, int maxKey, String[] values, int seed,
39 // 1. build a map with versions 38 int commitFrequency, boolean commitBeforeDiffCursor) {
40 Random r = new Random(seed); 39
41 VersionedMapImpl<Integer, String> versioned = (VersionedMapImpl<Integer, String>) store.createMap();
42 int largestCommit = -1; 40 int largestCommit = -1;
41 Map<Integer,Version> index2Version = new HashMap<>();
43 42
44 for (int i = 0; i < steps; i++) { 43 {
45 int index = i + 1; 44 // 1. build a map with versions
46 int nextKey = r.nextInt(maxKey); 45 Random r = new Random(seed);
47 String nextValue = values[r.nextInt(values.length)]; 46 VersionedMap<Integer, String> versioned = store.createMap();
48 try { 47 for (int i = 0; i < steps; i++) {
49 versioned.put(nextKey, nextValue); 48 int index = i + 1;
50 } catch (Exception exception) { 49 int nextKey = r.nextInt(maxKey);
51 exception.printStackTrace(); 50 String nextValue = values[r.nextInt(values.length)];
52 fail(scenario + ":" + index + ": exception happened: " + exception);
53 }
54 if (index % commitFrequency == 0) {
55 long version = versioned.commit();
56 largestCommit = (int) version;
57 }
58 if (index % 10000 == 0)
59 System.out.println(scenario + ":" + index + "/" + steps + " building finished");
60 }
61 // 2. create a non-versioned map,
62 VersionedMapImpl<Integer, String> moving = (VersionedMapImpl<Integer, String>) store.createMap();
63 Random r2 = new Random(seed + 1);
64
65 final int diffTravelFrequency = commitFrequency * 2;
66 for (int i = 0; i < steps; i++) {
67 int index = i + 1;
68 if (index % diffTravelFrequency == 0) {
69 // difftravel
70 long travelToVersion = r2.nextInt(largestCommit + 1);
71 DiffCursor<Integer, String> diffCursor = moving.getDiffCursor(travelToVersion);
72 moving.putAll(diffCursor);
73
74 } else {
75 // random puts
76 int nextKey = r2.nextInt(maxKey);
77 String nextValue = values[r2.nextInt(values.length)];
78 try { 51 try {
79 moving.put(nextKey, nextValue); 52 versioned.put(nextKey, nextValue);
80 } catch (Exception exception) { 53 } catch (Exception exception) {
81 exception.printStackTrace(); 54 exception.printStackTrace();
82 fail(scenario + ":" + index + ": exception happened: " + exception); 55 fail(scenario + ":" + index + ": exception happened: " + exception);
83 } 56 }
84 if (index % commitFrequency == 0) { 57 if (index % commitFrequency == 0) {
85 versioned.commit(); 58 Version version = versioned.commit();
59 index2Version.put(index,version);
60 largestCommit = index;
86 } 61 }
87 if (index % 10000 == 0) 62 if (index % 10000 == 0)
88 System.out.println(scenario + ":" + index + "/" + steps + " building finished"); 63 System.out.println(scenario + ":" + index + "/" + steps + " building finished");
89 } 64 }
90 } 65 }
91 66
67 {
68 // 2. create a non-versioned map,
69 VersionedMap<Integer, String> moving = store.createMap();
70 Random r2 = new Random(seed + 1);
71
72 final int diffTravelFrequency = commitFrequency * 2;
73 for (int i = 0; i < steps; i++) {
74 int index = i + 1;
75 if (index % diffTravelFrequency == 0) {
76 // diff-travel
77 int travelToVersion = r2.nextInt(largestCommit + 1);
78
79 VersionedMap<Integer, String> oracle = store.createMap(index2Version.get(travelToVersion));
80
81 if(commitBeforeDiffCursor) {
82 moving.commit();
83 }
84 DiffCursor<Integer, String> diffCursor = moving.getDiffCursor(index2Version.get(travelToVersion));
85 moving.putAll(diffCursor);
86 moving.commit();
87
88 MapTestEnvironment.compareTwoMaps(scenario + ":c" + index, oracle, moving);
89
90 moving.restore(index2Version.get(travelToVersion));
91
92 } else {
93 // random puts
94 int nextKey = r2.nextInt(maxKey);
95 String nextValue = values[r2.nextInt(values.length)];
96 try {
97 moving.put(nextKey, nextValue);
98 } catch (Exception exception) {
99 exception.printStackTrace();
100 fail(scenario + ":" + index + ": exception happened: " + exception);
101 }
102 if (index % 10000 == 0)
103 System.out.println(scenario + ":" + index + "/" + steps + " building finished");
104 }
105 }
106 }
92 } 107 }
93 108
94 @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") 109 public static final String title = "DiffCursor {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} " +
110 "commit frequency={5} seed={6} commit before diff={7} config={8}";
111
112 @ParameterizedTest(name = title)
95 @MethodSource 113 @MethodSource
96 @Timeout(value = 10) 114 @Timeout(value = 10)
97 @Tag("fuzz") 115 @Tag("fuzz")
98 void parametrizedFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, 116 void parametrizedFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault,
99 boolean evilHash) { 117 int commitFrequency, int seed, boolean commitBeforeDiffCursor,
100 runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, 118 VersionedMapStoreFactoryBuilder<Integer, String> builder) {
101 noKeys, noValues, commitFrequency, evilHash); 119 runFuzzTest("DiffCursorS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps,
120 noKeys, noValues, nullDefault, commitFrequency, commitBeforeDiffCursor, builder);
102 } 121 }
103 122
104 static Stream<Arguments> parametrizedFuzz() { 123 static Stream<Arguments> parametrizedFuzz() {
105 return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, 124 return FuzzTestUtils.permutationWithSize(new Object[]{100}, keyCounts, valueCounts, nullDefaultOptions,
106 new Object[] { 2, 3 }, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, 125 commitFrequencyOptions, randomSeedOptions, new Object[]{false,true}, storeConfigs);
107 new Object[] { false, true });
108 } 126 }
109 @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") 127
128 @ParameterizedTest(name = title)
110 @MethodSource 129 @MethodSource
111 @Tag("fuzz") 130 @Tag("fuzz")
112 @Tag("slow") 131 @Tag("slow")
113 void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, 132 void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency,
114 boolean evilHash) { 133 int seed, boolean commitBeforeDiffCursor, VersionedMapStoreFactoryBuilder<Integer, String> builder) {
115 runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, 134 runFuzzTest("DiffCursorS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues,
116 commitFrequency, evilHash); 135 nullDefault, commitFrequency, commitBeforeDiffCursor, builder);
117 } 136 }
118 137
119 static Stream<Arguments> parametrizedSlowFuzz() { 138 static Stream<Arguments> parametrizedSlowFuzz() {
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java
index ab2b9435..3b55434c 100644
--- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java
+++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java
@@ -5,95 +5,96 @@
5 */ 5 */
6package tools.refinery.store.map.tests.fuzz; 6package tools.refinery.store.map.tests.fuzz;
7 7
8import static org.junit.jupiter.api.Assertions.assertEquals;
9import static org.junit.jupiter.api.Assertions.fail;
10
11import java.util.Collections;
12import java.util.LinkedList;
13import java.util.List;
14import java.util.stream.Stream;
15
16import org.junit.jupiter.api.Tag; 8import org.junit.jupiter.api.Tag;
17import org.junit.jupiter.api.Timeout; 9import org.junit.jupiter.api.Timeout;
18import org.junit.jupiter.params.ParameterizedTest; 10import org.junit.jupiter.params.ParameterizedTest;
19import org.junit.jupiter.params.provider.Arguments; 11import org.junit.jupiter.params.provider.Arguments;
20import org.junit.jupiter.params.provider.MethodSource; 12import org.junit.jupiter.params.provider.MethodSource;
21
22import tools.refinery.store.map.ContinousHashProvider;
23import tools.refinery.store.map.VersionedMapStore; 13import tools.refinery.store.map.VersionedMapStore;
24import tools.refinery.store.map.VersionedMapStoreImpl; 14import tools.refinery.store.map.VersionedMapStoreFactoryBuilder;
25import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; 15import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils;
26import tools.refinery.store.map.tests.utils.MapTestEnvironment; 16import tools.refinery.store.map.tests.utils.MapTestEnvironment;
27 17
18import java.util.Collections;
19import java.util.LinkedList;
20import java.util.List;
21import java.util.stream.Stream;
22
23import static org.junit.jupiter.api.Assertions.assertEquals;
24import static org.junit.jupiter.api.Assertions.fail;
25import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*;
26
28class MultiThreadFuzzTest { 27class MultiThreadFuzzTest {
29 public static final int noThreads = 32; 28 public static final int noThreads = 10;
30 29
31 private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency, 30 private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, int commitFrequency, VersionedMapStoreFactoryBuilder<Integer, String> builder) {
32 boolean evilHash) { 31 String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault);
33 String[] values = MapTestEnvironment.prepareValues(maxValue); 32
34 ContinousHashProvider<Integer> chp = MapTestEnvironment.prepareHashProvider(evilHash); 33 VersionedMapStore<Integer, String> store = builder.defaultValue(values[0]).build().createOne();
35 34
36 VersionedMapStore<Integer, String> store = new VersionedMapStoreImpl<Integer, String>(chp, values[0]);
37
38 // initialize runnables 35 // initialize runnables
39 MultiThreadTestRunnable[] runnables = new MultiThreadTestRunnable[noThreads]; 36 MultiThreadTestRunnable[] runnables = new MultiThreadTestRunnable[noThreads];
40 for(int i = 0; i<noThreads; i++) { 37 for (int i = 0; i < noThreads; i++) {
41 runnables[i] = new MultiThreadTestRunnable(scenario+"-T"+(i+1), store, steps, maxKey, values, seed, commitFrequency); 38 runnables[i] = new MultiThreadTestRunnable(scenario + "-T" + (i + 1), store, steps, maxKey, values, seed
39 , commitFrequency);
42 } 40 }
43 41
44 // initialize threads 42 // initialize threads
45 Thread[] threads = new Thread[noThreads]; 43 Thread[] threads = new Thread[noThreads];
46 for(int i = 0; i<noThreads; i++) { 44 for (int i = 0; i < noThreads; i++) {
47 threads[i] = new Thread(runnables[i]); 45 threads[i] = new Thread(runnables[i]);
48 } 46 }
49 47
50 // start threads; 48 // start threads;
51 for(int i = 0; i<noThreads; i++) { 49 for (int i = 0; i < noThreads; i++) {
52 threads[i].start(); 50 runnables[i].run();
51 //threads[i].start();
53 } 52 }
54 53
55 // wait all the threads; 54 // wait all the threads;
56 for(int i = 0; i<noThreads; i++) { 55 for (int i = 0; i < noThreads; i++) {
57 try { 56 try {
58 threads[i].join(); 57 threads[i].join();
59 } catch (InterruptedException e) { 58 } catch (InterruptedException e) {
60 fail("Thread "+i+" interrupted."); 59 fail("Thread " + i + " interrupted.");
61 } 60 }
62 } 61 }
63 62
64 // collect errors 63 // collect errors
65 List<Throwable> errors = new LinkedList<>(); 64 List<Throwable> errors = new LinkedList<>();
66 for(int i = 0; i<noThreads; i++) { 65 for (int i = 0; i < noThreads; i++) {
67 errors.addAll(runnables[i].getErrors()); 66 errors.addAll(runnables[i].getErrors());
68 } 67 }
69 68
70 assertEquals(Collections.EMPTY_LIST, errors); 69 assertEquals(Collections.EMPTY_LIST, errors);
71 } 70 }
72 71
73 @ParameterizedTest(name = "Multithread {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") 72 static final String title = "MultiThread {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} commit " +
73 "frequency={5} seed={6} config={7}";
74
75 @ParameterizedTest(name = title)
74 @MethodSource 76 @MethodSource
75 @Timeout(value = 10) 77 @Timeout(value = 10)
76 @Tag("fuzz") 78 @Tag("fuzz")
77 void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, 79 void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean defaultNull,
78 boolean evilHash) { 80 int commitFrequency, int seed, VersionedMapStoreFactoryBuilder<Integer, String> builder) {
79 runFuzzTest("MultithreadS" + steps + "K" + noKeys + "V" + noValues + "CF" + commitFrequency + "s" + seed, seed, steps, noKeys, noValues, 81 runFuzzTest("MultiThreadS" + steps + "K" + noKeys + "V" + noValues + defaultNull + "CF" + commitFrequency +
80 commitFrequency, evilHash); 82 "s" + seed, seed, steps, noKeys, noValues, defaultNull, commitFrequency, builder);
81 } 83 }
82 84
83 static Stream<Arguments> parametrizedFastFuzz() { 85 static Stream<Arguments> parametrizedFastFuzz() {
84 return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, 86 return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions,
85 new Object[] { 2, 3 }, new Object[] { 10, 100 }, new Object[] { 1, 2, 3 }, 87 new Object[]{10, 100}, randomSeedOptions, storeConfigs);
86 new Object[] { false, true });
87 } 88 }
88 89
89 @ParameterizedTest(name = "Multithread {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") 90 @ParameterizedTest(name = title)
90 @MethodSource 91 @MethodSource
91 @Tag("fuzz") 92 @Tag("fuzz")
92 @Tag("slow") 93 @Tag("slow")
93 void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, 94 void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault,
94 boolean evilHash) { 95 int commitFrequency, int seed, VersionedMapStoreFactoryBuilder<Integer, String> builder) {
95 runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, 96 runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues,
96 commitFrequency, evilHash); 97 nullDefault, commitFrequency, builder);
97 } 98 }
98 99
99 static Stream<Arguments> parametrizedSlowFuzz() { 100 static Stream<Arguments> parametrizedSlowFuzz() {
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java
index 502c8362..dfe46bae 100644
--- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java
+++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java
@@ -13,22 +13,23 @@ import java.util.List;
13import java.util.Map; 13import java.util.Map;
14import java.util.Random; 14import java.util.Random;
15 15
16import tools.refinery.store.map.Version;
17import tools.refinery.store.map.VersionedMap;
16import tools.refinery.store.map.VersionedMapStore; 18import tools.refinery.store.map.VersionedMapStore;
17import tools.refinery.store.map.internal.VersionedMapImpl;
18import tools.refinery.store.map.tests.utils.MapTestEnvironment; 19import tools.refinery.store.map.tests.utils.MapTestEnvironment;
19 20
20public class MultiThreadTestRunnable implements Runnable { 21public class MultiThreadTestRunnable implements Runnable {
21 String scenario; 22 final String scenario;
22 VersionedMapStore<Integer, String> store; 23 final VersionedMapStore<Integer, String> store;
23 int steps; 24 final int steps;
24 int maxKey; 25 final int maxKey;
25 String[] values; 26 final String[] values;
26 int seed; 27 final int seed;
27 int commitFrequency; 28 final int commitFrequency;
28 List<Throwable> errors = new LinkedList<>(); 29 final List<Throwable> errors = new LinkedList<>();
29 30
30 public MultiThreadTestRunnable(String scenario, VersionedMapStore<Integer, String> store, int steps, 31 public MultiThreadTestRunnable(String scenario, VersionedMapStore<Integer, String> store, int steps,
31 int maxKey, String[] values, int seed, int commitFrequency) { 32 int maxKey, String[] values, int seed, int commitFrequency) {
32 super(); 33 super();
33 this.scenario = scenario; 34 this.scenario = scenario;
34 this.store = store; 35 this.store = store;
@@ -43,17 +44,25 @@ public class MultiThreadTestRunnable implements Runnable {
43 AssertionError error = new AssertionError(message); 44 AssertionError error = new AssertionError(message);
44 errors.add(error); 45 errors.add(error);
45 } 46 }
46 47
47 public List<Throwable> getErrors() { 48 public List<Throwable> getErrors() {
48 return errors; 49 return errors;
49 } 50 }
50 51
51 @Override 52 @Override
52 public void run() { 53 public void run() {
54 try{
55 task();
56 } catch(Exception e) {
57 e.printStackTrace();
58 }
59 }
60
61 private void task() {
53 // 1. build a map with versions 62 // 1. build a map with versions
54 Random r = new Random(seed); 63 Random r = new Random(seed);
55 VersionedMapImpl<Integer, String> versioned = (VersionedMapImpl<Integer, String>) store.createMap(); 64 VersionedMap<Integer, String> versioned = store.createMap();
56 Map<Integer, Long> index2Version = new HashMap<>(); 65 Map<Integer, Version> index2Version = new HashMap<>();
57 66
58 for (int i = 0; i < steps; i++) { 67 for (int i = 0; i < steps; i++) {
59 int index = i + 1; 68 int index = i + 1;
@@ -66,15 +75,15 @@ public class MultiThreadTestRunnable implements Runnable {
66 logAndThrowError(scenario + ":" + index + ": exception happened: " + exception); 75 logAndThrowError(scenario + ":" + index + ": exception happened: " + exception);
67 } 76 }
68 if (index % commitFrequency == 0) { 77 if (index % commitFrequency == 0) {
69 long version = versioned.commit(); 78 Version version = versioned.commit();
70 index2Version.put(i, version); 79 index2Version.put(i, version);
71 } 80 }
72 MapTestEnvironment.printStatus(scenario, index, steps, "building"); 81 MapTestEnvironment.printStatus(scenario, index, steps, "building");
73 } 82 }
74 // 2. create a non-versioned 83 // 2. create a non-versioned
75 VersionedMapImpl<Integer, String> reference = (VersionedMapImpl<Integer, String>) store.createMap(); 84 VersionedMap<Integer, String> reference = store.createMap();
76 r = new Random(seed); 85 r = new Random(seed);
77 Random r2 = new Random(seed+1); 86 Random r2 = new Random(seed + 1);
78 87
79 for (int i = 0; i < steps; i++) { 88 for (int i = 0; i < steps; i++) {
80 int index = i + 1; 89 int index = i + 1;
@@ -89,13 +98,16 @@ public class MultiThreadTestRunnable implements Runnable {
89 // go back to an existing state and compare to the reference 98 // go back to an existing state and compare to the reference
90 if (index % (commitFrequency) == 0) { 99 if (index % (commitFrequency) == 0) {
91 versioned.restore(index2Version.get(i)); 100 versioned.restore(index2Version.get(i));
92 MapTestEnvironment.compareTwoMaps(scenario + ":" + index, reference, versioned,errors); 101 MapTestEnvironment.compareTwoMaps(scenario + ":" + index, reference, versioned, null);
93 102
94 // go back to a random state (probably created by another thread) 103 // go back to a random state (probably created by another thread)
95 List<Long> states = new ArrayList<>(store.getStates()); 104 List<Version> states = new ArrayList<>(index2Version.values());
105 //states.sort(Long::compare);
96 Collections.shuffle(states, r2); 106 Collections.shuffle(states, r2);
97 for(Long state : states.subList(0, Math.min(states.size(), 100))) { 107 for (Version state : states.subList(0, Math.min(states.size(), 100))) {
98 versioned.restore(state); 108 versioned.restore(state);
109 var clean = store.createMap(state);
110 MapTestEnvironment.compareTwoMaps(scenario + ":" + index, clean, versioned, null);
99 } 111 }
100 versioned.restore(index2Version.get(i)); 112 versioned.restore(index2Version.get(i));
101 } 113 }
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java
index 32dde0da..fdcd7f9f 100644
--- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java
+++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.map.tests.fuzz; 6package tools.refinery.store.map.tests.fuzz;
7 7
8import static org.junit.jupiter.api.Assertions.fail; 8import static org.junit.jupiter.api.Assertions.fail;
9import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*;
9 10
10import java.util.Random; 11import java.util.Random;
11import java.util.stream.Stream; 12import java.util.stream.Stream;
@@ -16,21 +17,18 @@ import org.junit.jupiter.params.ParameterizedTest;
16import org.junit.jupiter.params.provider.Arguments; 17import org.junit.jupiter.params.provider.Arguments;
17import org.junit.jupiter.params.provider.MethodSource; 18import org.junit.jupiter.params.provider.MethodSource;
18 19
19import tools.refinery.store.map.ContinousHashProvider; 20import tools.refinery.store.map.*;
20import tools.refinery.store.map.VersionedMapStore;
21import tools.refinery.store.map.VersionedMapStoreImpl;
22import tools.refinery.store.map.internal.VersionedMapImpl;
23import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; 21import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils;
24import tools.refinery.store.map.tests.utils.MapTestEnvironment; 22import tools.refinery.store.map.tests.utils.MapTestEnvironment;
25 23
26class MutableFuzzTest { 24class MutableFuzzTest {
27 private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean evilHash) { 25 private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue,
28 String[] values = MapTestEnvironment.prepareValues(maxValue); 26 boolean nullDefault, VersionedMapStoreFactoryBuilder<Integer, String> builder) {
29 ContinousHashProvider<Integer> chp = MapTestEnvironment.prepareHashProvider(evilHash); 27 String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault);
30 28
31 VersionedMapStore<Integer, String> store = new VersionedMapStoreImpl<Integer, String>(chp, values[0]); 29 VersionedMapStore<Integer, String> store = builder.defaultValue(values[0]).build().createOne();
32 VersionedMapImpl<Integer, String> sut = (VersionedMapImpl<Integer, String>) store.createMap(); 30 VersionedMap<Integer, String> sut = store.createMap();
33 MapTestEnvironment<Integer, String> e = new MapTestEnvironment<Integer, String>(sut); 31 MapTestEnvironment<Integer, String> e = new MapTestEnvironment<>(sut);
34 32
35 Random r = new Random(seed); 33 Random r = new Random(seed);
36 34
@@ -38,24 +36,14 @@ class MutableFuzzTest {
38 } 36 }
39 37
40 private void iterativeRandomPuts(String scenario, int steps, int maxKey, String[] values, 38 private void iterativeRandomPuts(String scenario, int steps, int maxKey, String[] values,
41 MapTestEnvironment<Integer, String> e, Random r) { 39 MapTestEnvironment<Integer, String> e, Random r) {
42 int stopAt = -1;
43 for (int i = 0; i < steps; i++) { 40 for (int i = 0; i < steps; i++) {
44 int index = i + 1; 41 int index = i + 1;
45 int nextKey = r.nextInt(maxKey); 42 int nextKey = r.nextInt(maxKey);
46 String nextValue = values[r.nextInt(values.length)]; 43 String nextValue = values[r.nextInt(values.length)];
47 if (index == stopAt) { 44
48 System.out.println("issue!");
49 System.out.println("State before:");
50 e.printComparison();
51 e.sut.prettyPrint();
52 System.out.println("Next: put(" + nextKey + "," + nextValue + ")");
53 }
54 try { 45 try {
55 e.put(nextKey, nextValue); 46 e.put(nextKey, nextValue);
56 if (index == stopAt) {
57 e.sut.prettyPrint();
58 }
59 e.checkEquivalence(scenario + ":" + index); 47 e.checkEquivalence(scenario + ":" + index);
60 } catch (Exception exception) { 48 } catch (Exception exception) {
61 exception.printStackTrace(); 49 exception.printStackTrace();
@@ -65,30 +53,34 @@ class MutableFuzzTest {
65 } 53 }
66 } 54 }
67 55
68 @ParameterizedTest(name = "Mutable {index}/{0} Steps={1} Keys={2} Values={3} seed={4} evil-hash={5}") 56 final String title = "Mutable {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} seed={5} " +
57 "config={6}";
58
59 @ParameterizedTest(name = title)
69 @MethodSource 60 @MethodSource
70 @Timeout(value = 10) 61 @Timeout(value = 10)
71 @Tag("fuzz") 62 @Tag("fuzz")
72 void parametrizedFuzz(int test, int steps, int noKeys, int noValues, int seed, boolean evilHash) { 63 void parametrizedFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean defaultNull, int seed,
64 VersionedMapStoreFactoryBuilder<Integer, String> builder) {
73 runFuzzTest( 65 runFuzzTest(
74 "MutableS" + steps + "K" + noKeys + "V" + noValues + "s" + seed + "H" + (evilHash ? "Evil" : "Normal"), 66 "MutableS" + steps + "K" + noKeys + "V" + noValues + "s" + seed,
75 seed, steps, noKeys, noValues, evilHash); 67 seed, steps, noKeys, noValues, defaultNull, builder);
76 } 68 }
77 69
78 static Stream<Arguments> parametrizedFuzz() { 70 static Stream<Arguments> parametrizedFuzz() {
79 return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, 71 return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions,
80 new Object[] { 3, 32, 32 * 32, 32 * 32 * 32 * 32 }, new Object[] { 2, 3 }, new Object[] { 1, 2, 3 }, 72 randomSeedOptions, storeConfigs);
81 new Object[] { false, true });
82 } 73 }
83 74
84 @ParameterizedTest(name = "Mutable {index}/{0} Steps={1} Keys={2} Values={3} seed={4} evil-hash={5}") 75 @ParameterizedTest(name = title)
85 @MethodSource 76 @MethodSource
86 @Tag("fuzz") 77 @Tag("fuzz")
87 @Tag("slow") 78 @Tag("slow")
88 void parametrizedSlowFuzz(int test, int steps, int noKeys, int noValues, int seed, boolean evilHash) { 79 void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int seed,
80 VersionedMapStoreFactoryBuilder<Integer, String> builder) {
89 runFuzzTest( 81 runFuzzTest(
90 "MutableS" + steps + "K" + noKeys + "V" + noValues + "s" + seed + "H" + (evilHash ? "Evil" : "Normal"), 82 "MutableS" + steps + "K" + noKeys + "V" + noValues + "s" + seed,
91 seed, steps, noKeys, noValues, evilHash); 83 seed, steps, noKeys, noValues, nullDefault, builder);
92 } 84 }
93 85
94 static Stream<Arguments> parametrizedSlowFuzz() { 86 static Stream<Arguments> parametrizedSlowFuzz() {
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java
index 347c49be..abfb4791 100644
--- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java
+++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.map.tests.fuzz; 6package tools.refinery.store.map.tests.fuzz;
7 7
8import static org.junit.jupiter.api.Assertions.fail; 8import static org.junit.jupiter.api.Assertions.fail;
9import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*;
9 10
10import java.util.Random; 11import java.util.Random;
11import java.util.stream.Stream; 12import java.util.stream.Stream;
@@ -16,22 +17,22 @@ import org.junit.jupiter.params.ParameterizedTest;
16import org.junit.jupiter.params.provider.Arguments; 17import org.junit.jupiter.params.provider.Arguments;
17import org.junit.jupiter.params.provider.MethodSource; 18import org.junit.jupiter.params.provider.MethodSource;
18 19
19import tools.refinery.store.map.ContinousHashProvider; 20import tools.refinery.store.map.ContinuousHashProvider;
20import tools.refinery.store.map.VersionedMapStore; 21import tools.refinery.store.map.VersionedMapStore;
21import tools.refinery.store.map.VersionedMapStoreImpl; 22import tools.refinery.store.map.internal.state.VersionedMapStoreStateImpl;
22import tools.refinery.store.map.internal.VersionedMapImpl; 23import tools.refinery.store.map.internal.state.VersionedMapStateImpl;
23import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; 24import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils;
24import tools.refinery.store.map.tests.utils.MapTestEnvironment; 25import tools.refinery.store.map.tests.utils.MapTestEnvironment;
25 26
26class MutableImmutableCompareFuzzTest { 27class MutableImmutableCompareFuzzTest {
27 private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency, 28 private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue,
28 boolean evilHash) { 29 boolean nullDefault, int commitFrequency, boolean evilHash) {
29 String[] values = MapTestEnvironment.prepareValues(maxValue); 30 String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault);
30 ContinousHashProvider<Integer> chp = MapTestEnvironment.prepareHashProvider(evilHash); 31 ContinuousHashProvider<Integer> chp = MapTestEnvironment.prepareHashProvider(evilHash);
31 32
32 VersionedMapStore<Integer, String> store = new VersionedMapStoreImpl<Integer, String>(chp, values[0]); 33 VersionedMapStore<Integer, String> store = new VersionedMapStoreStateImpl<>(chp, values[0]);
33 VersionedMapImpl<Integer, String> immutable = (VersionedMapImpl<Integer, String>) store.createMap(); 34 VersionedMapStateImpl<Integer, String> immutable = (VersionedMapStateImpl<Integer, String>) store.createMap();
34 VersionedMapImpl<Integer, String> mutable = (VersionedMapImpl<Integer, String>) store.createMap(); 35 VersionedMapStateImpl<Integer, String> mutable = (VersionedMapStateImpl<Integer, String>) store.createMap();
35 36
36 Random r = new Random(seed); 37 Random r = new Random(seed);
37 38
@@ -39,9 +40,9 @@ class MutableImmutableCompareFuzzTest {
39 commitFrequency); 40 commitFrequency);
40 } 41 }
41 42
42 private void iterativeRandomPutsAndCommitsAndCompare(String scenario, VersionedMapImpl<Integer, String> immutable, 43 private void iterativeRandomPutsAndCommitsAndCompare(String scenario, VersionedMapStateImpl<Integer, String> immutable,
43 VersionedMapImpl<Integer, String> mutable, int steps, int maxKey, String[] values, Random r, 44 VersionedMapStateImpl<Integer, String> mutable, int steps, int maxKey, String[] values, Random r,
44 int commitFrequency) { 45 int commitFrequency) {
45 for (int i = 0; i < steps; i++) { 46 for (int i = 0; i < steps; i++) {
46 int index = i + 1; 47 int index = i + 1;
47 int nextKey = r.nextInt(maxKey); 48 int nextKey = r.nextInt(maxKey);
@@ -62,30 +63,31 @@ class MutableImmutableCompareFuzzTest {
62 } 63 }
63 } 64 }
64 65
65 @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") 66 @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} " +
67 "commit frequency={5} seed={6} evil-hash={7}")
66 @MethodSource 68 @MethodSource
67 @Timeout(value = 10) 69 @Timeout(value = 10)
68 @Tag("fuzz") 70 @Tag("fuzz")
69 void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, 71 void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency,
70 boolean evilHash) { 72 int seed, boolean evilHash) {
71 runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, 73 runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps,
72 noKeys, noValues, commitFrequency, evilHash); 74 noKeys, noValues, nullDefault, commitFrequency, evilHash);
73 } 75 }
74 76
75 static Stream<Arguments> parametrizedFastFuzz() { 77 static Stream<Arguments> parametrizedFastFuzz() {
76 return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, 78 return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions,
77 new Object[] { 2, 3 }, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, 79 commitFrequencyOptions, randomSeedOptions, new Object[]{false, true});
78 new Object[] { false, true });
79 } 80 }
80 81
81 @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") 82 @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} " +
83 "commit frequency={5} seed={6} evil-hash={7}")
82 @MethodSource 84 @MethodSource
83 @Tag("fuzz") 85 @Tag("fuzz")
84 @Tag("slow") 86 @Tag("slow")
85 void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, 87 void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency,
86 boolean evilHash) { 88 int seed, boolean evilHash) {
87 runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, 89 runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps,
88 noKeys, noValues, commitFrequency, evilHash); 90 noKeys, noValues, nullDefault, commitFrequency, evilHash);
89 } 91 }
90 92
91 static Stream<Arguments> parametrizedSlowFuzz() { 93 static Stream<Arguments> parametrizedSlowFuzz() {
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java
index f7b9d61e..5c768788 100644
--- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java
+++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java
@@ -5,43 +5,43 @@
5 */ 5 */
6package tools.refinery.store.map.tests.fuzz; 6package tools.refinery.store.map.tests.fuzz;
7 7
8import static org.junit.jupiter.api.Assertions.fail;
9
10import java.util.HashMap;
11import java.util.Map;
12import java.util.Random;
13import java.util.stream.Stream;
14
15import org.junit.jupiter.api.Tag; 8import org.junit.jupiter.api.Tag;
16import org.junit.jupiter.api.Timeout; 9import org.junit.jupiter.api.Timeout;
17import org.junit.jupiter.params.ParameterizedTest; 10import org.junit.jupiter.params.ParameterizedTest;
18import org.junit.jupiter.params.provider.Arguments; 11import org.junit.jupiter.params.provider.Arguments;
19import org.junit.jupiter.params.provider.MethodSource; 12import org.junit.jupiter.params.provider.MethodSource;
20 13import tools.refinery.store.map.Version;
21import tools.refinery.store.map.ContinousHashProvider; 14import tools.refinery.store.map.VersionedMap;
22import tools.refinery.store.map.VersionedMapStore; 15import tools.refinery.store.map.VersionedMapStore;
23import tools.refinery.store.map.VersionedMapStoreImpl; 16import tools.refinery.store.map.VersionedMapStoreFactoryBuilder;
24import tools.refinery.store.map.internal.VersionedMapImpl;
25import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; 17import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils;
26import tools.refinery.store.map.tests.utils.MapTestEnvironment; 18import tools.refinery.store.map.tests.utils.MapTestEnvironment;
27 19
20import java.util.HashMap;
21import java.util.Map;
22import java.util.Random;
23import java.util.stream.Stream;
24
25import static org.junit.jupiter.api.Assertions.fail;
26import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*;
27
28class RestoreFuzzTest { 28class RestoreFuzzTest {
29 private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency, 29 private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue,
30 boolean evilHash) { 30 boolean nullDefault, int commitFrequency,
31 String[] values = MapTestEnvironment.prepareValues(maxValue); 31 VersionedMapStoreFactoryBuilder<Integer, String> builder) {
32 ContinousHashProvider<Integer> chp = MapTestEnvironment.prepareHashProvider(evilHash); 32 String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault);
33 33
34 VersionedMapStore<Integer, String> store = new VersionedMapStoreImpl<Integer, String>(chp, values[0]); 34 VersionedMapStore<Integer, String> store = builder.defaultValue(values[0]).build().createOne();
35 35
36 iterativeRandomPutsAndCommitsThenRestore(scenario, store, steps, maxKey, values, seed, commitFrequency); 36 iterativeRandomPutsAndCommitsThenRestore(scenario, store, steps, maxKey, values, seed, commitFrequency);
37 } 37 }
38 38
39 private void iterativeRandomPutsAndCommitsThenRestore(String scenario, VersionedMapStore<Integer, String> store, 39 private void iterativeRandomPutsAndCommitsThenRestore(String scenario, VersionedMapStore<Integer, String> store,
40 int steps, int maxKey, String[] values, int seed, int commitFrequency) { 40 int steps, int maxKey, String[] values, int seed, int commitFrequency) {
41 // 1. build a map with versions 41 // 1. build a map with versions
42 Random r = new Random(seed); 42 Random r = new Random(seed);
43 VersionedMapImpl<Integer, String> versioned = (VersionedMapImpl<Integer, String>) store.createMap(); 43 VersionedMap<Integer, String> versioned = store.createMap();
44 Map<Integer, Long> index2Version = new HashMap<>(); 44 Map<Integer, Version> index2Version = new HashMap<>();
45 45
46 for (int i = 0; i < steps; i++) { 46 for (int i = 0; i < steps; i++) {
47 int index = i + 1; 47 int index = i + 1;
@@ -54,13 +54,13 @@ class RestoreFuzzTest {
54 fail(scenario + ":" + index + ": exception happened: " + exception); 54 fail(scenario + ":" + index + ": exception happened: " + exception);
55 } 55 }
56 if (index % commitFrequency == 0) { 56 if (index % commitFrequency == 0) {
57 long version = versioned.commit(); 57 Version version = versioned.commit();
58 index2Version.put(i, version); 58 index2Version.put(i, version);
59 } 59 }
60 MapTestEnvironment.printStatus(scenario, index, steps, "building"); 60 MapTestEnvironment.printStatus(scenario, index, steps, "building");
61 } 61 }
62 // 2. create a non-versioned and 62 // 2. create a non-versioned and
63 VersionedMapImpl<Integer, String> reference = (VersionedMapImpl<Integer, String>) store.createMap(); 63 VersionedMap<Integer, String> reference = store.createMap();
64 r = new Random(seed); 64 r = new Random(seed);
65 65
66 for (int i = 0; i < steps; i++) { 66 for (int i = 0; i < steps; i++) {
@@ -82,30 +82,32 @@ class RestoreFuzzTest {
82 82
83 } 83 }
84 84
85 @ParameterizedTest(name = "Restore {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") 85 public static final String title = "Commit {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit frequency={5} " +
86 "seed={6} config={7}";
87
88 @ParameterizedTest(name = title)
86 @MethodSource 89 @MethodSource
87 @Timeout(value = 10) 90 @Timeout(value = 10)
88 @Tag("smoke") 91 @Tag("smoke")
89 void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, 92 void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency,
90 boolean evilHash) { 93 int seed, VersionedMapStoreFactoryBuilder<Integer, String> builder) {
91 runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, 94 runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues,
92 commitFrequency, evilHash); 95 nullDefault, commitFrequency, builder);
93 } 96 }
94 97
95 static Stream<Arguments> parametrizedFastFuzz() { 98 static Stream<Arguments> parametrizedFastFuzz() {
96 return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, 99 return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions,
97 new Object[] { 2, 3 }, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, 100 commitFrequencyOptions, randomSeedOptions, storeConfigs);
98 new Object[] { false, true });
99 } 101 }
100 102
101 @ParameterizedTest(name = "Restore {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") 103 @ParameterizedTest(name = title)
102 @MethodSource 104 @MethodSource
103 @Tag("smoke") 105 @Tag("smoke")
104 @Tag("slow") 106 @Tag("slow")
105 void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, 107 void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency,
106 boolean evilHash) { 108 int seed, VersionedMapStoreFactoryBuilder<Integer, String> builder) {
107 runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, 109 runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues,
108 commitFrequency, evilHash); 110 nullDefault, commitFrequency, builder);
109 } 111 }
110 112
111 static Stream<Arguments> parametrizedSlowFuzz() { 113 static Stream<Arguments> parametrizedSlowFuzz() {
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java
index 4b4172d0..299c94b1 100644
--- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java
+++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java
@@ -18,61 +18,64 @@ import org.junit.jupiter.params.ParameterizedTest;
18import org.junit.jupiter.params.provider.Arguments; 18import org.junit.jupiter.params.provider.Arguments;
19import org.junit.jupiter.params.provider.MethodSource; 19import org.junit.jupiter.params.provider.MethodSource;
20 20
21import tools.refinery.store.map.ContinousHashProvider; 21import tools.refinery.store.map.ContinuousHashProvider;
22import tools.refinery.store.map.Version;
22import tools.refinery.store.map.VersionedMapStore; 23import tools.refinery.store.map.VersionedMapStore;
23import tools.refinery.store.map.VersionedMapStoreImpl; 24import tools.refinery.store.map.internal.state.VersionedMapStoreStateImpl;
24import tools.refinery.store.map.internal.VersionedMapImpl; 25import tools.refinery.store.map.internal.state.VersionedMapStateImpl;
25import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; 26import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils;
26import tools.refinery.store.map.tests.utils.MapTestEnvironment; 27import tools.refinery.store.map.tests.utils.MapTestEnvironment;
27 28
29import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*;
30
28class SharedStoreFuzzTest { 31class SharedStoreFuzzTest {
29 private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency, 32 private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue,
30 boolean evilHash) { 33 boolean nullDefault, int commitFrequency, boolean evilHash) {
31 String[] values = MapTestEnvironment.prepareValues(maxValue); 34 String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault);
32 ContinousHashProvider<Integer> chp = MapTestEnvironment.prepareHashProvider(evilHash); 35 ContinuousHashProvider<Integer> chp = MapTestEnvironment.prepareHashProvider(evilHash);
33 36
34 List<VersionedMapStore<Integer, String>> stores = VersionedMapStoreImpl.createSharedVersionedMapStores(5, chp, values[0]); 37 List<VersionedMapStore<Integer, String>> stores = VersionedMapStoreStateImpl.createSharedVersionedMapStores(5, chp, values[0]);
35 38
36 iterativeRandomPutsAndCommitsThenRestore(scenario, stores, steps, maxKey, values, seed, commitFrequency); 39 iterativeRandomPutsAndCommitsThenRestore(scenario, stores, steps, maxKey, values, seed, commitFrequency);
37 } 40 }
38 41
39 private void iterativeRandomPutsAndCommitsThenRestore(String scenario, List<VersionedMapStore<Integer, String>> stores, 42 private void iterativeRandomPutsAndCommitsThenRestore(String scenario, List<VersionedMapStore<Integer, String>> stores,
40 int steps, int maxKey, String[] values, int seed, int commitFrequency) { 43 int steps, int maxKey, String[] values, int seed, int commitFrequency) {
41 // 1. maps with versions 44 // 1. maps with versions
42 Random r = new Random(seed); 45 Random r = new Random(seed);
43 List<VersionedMapImpl<Integer, String>> versioneds = new LinkedList<>(); 46 List<VersionedMapStateImpl<Integer, String>> versioneds = new LinkedList<>();
44 for(VersionedMapStore<Integer, String> store : stores) { 47 for (VersionedMapStore<Integer, String> store : stores) {
45 versioneds.add((VersionedMapImpl<Integer, String>) store.createMap()); 48 versioneds.add((VersionedMapStateImpl<Integer, String>) store.createMap());
46 } 49 }
47 50
48 List<Map<Integer, Long>> index2Version = new LinkedList<>(); 51 List<Map<Integer, Version>> index2Version = new LinkedList<>();
49 for(int i = 0; i<stores.size(); i++) { 52 for (int i = 0; i < stores.size(); i++) {
50 index2Version.add(new HashMap<>()); 53 index2Version.add(new HashMap<>());
51 } 54 }
52 55
53 for (int i = 0; i < steps; i++) { 56 for (int i = 0; i < steps; i++) {
54 int stepIndex = i + 1; 57 int stepIndex = i + 1;
55 for (int storeIndex = 0; storeIndex<versioneds.size(); storeIndex++) { 58 for (int storeIndex = 0; storeIndex < versioneds.size(); storeIndex++) {
56 int nextKey = r.nextInt(maxKey); 59 int nextKey = r.nextInt(maxKey);
57 String nextValue = values[r.nextInt(values.length)]; 60 String nextValue = values[r.nextInt(values.length)];
58 versioneds.get(storeIndex).put(nextKey, nextValue); 61 versioneds.get(storeIndex).put(nextKey, nextValue);
59 if (stepIndex % commitFrequency == 0) { 62 if (stepIndex % commitFrequency == 0) {
60 long version = versioneds.get(storeIndex).commit(); 63 Version version = versioneds.get(storeIndex).commit();
61 index2Version.get(storeIndex).put(i, version); 64 index2Version.get(storeIndex).put(i, version);
62 } 65 }
63 MapTestEnvironment.printStatus(scenario, stepIndex, steps, "building"); 66 MapTestEnvironment.printStatus(scenario, stepIndex, steps, "building");
64 } 67 }
65 } 68 }
66 // 2. create a non-versioned and 69 // 2. create a non-versioned and
67 List<VersionedMapImpl<Integer, String>> reference = new LinkedList<>(); 70 List<VersionedMapStateImpl<Integer, String>> reference = new LinkedList<>();
68 for(VersionedMapStore<Integer, String> store : stores) { 71 for (VersionedMapStore<Integer, String> store : stores) {
69 reference.add((VersionedMapImpl<Integer, String>) store.createMap()); 72 reference.add((VersionedMapStateImpl<Integer, String>) store.createMap());
70 } 73 }
71 r = new Random(seed); 74 r = new Random(seed);
72 75
73 for (int i = 0; i < steps; i++) { 76 for (int i = 0; i < steps; i++) {
74 int index = i + 1; 77 int index = i + 1;
75 for (int storeIndex = 0; storeIndex<versioneds.size(); storeIndex++) { 78 for (int storeIndex = 0; storeIndex < versioneds.size(); storeIndex++) {
76 int nextKey = r.nextInt(maxKey); 79 int nextKey = r.nextInt(maxKey);
77 String nextValue = values[r.nextInt(values.length)]; 80 String nextValue = values[r.nextInt(values.length)];
78 reference.get(storeIndex).put(nextKey, nextValue); 81 reference.get(storeIndex).put(nextKey, nextValue);
@@ -81,35 +84,36 @@ class SharedStoreFuzzTest {
81 MapTestEnvironment.compareTwoMaps(scenario + ":" + index, reference.get(storeIndex), versioneds.get(storeIndex)); 84 MapTestEnvironment.compareTwoMaps(scenario + ":" + index, reference.get(storeIndex), versioneds.get(storeIndex));
82 } 85 }
83 } 86 }
84 MapTestEnvironment.printStatus(scenario, index, steps, "comparison"); 87 MapTestEnvironment.printStatus(scenario, index, steps, "comparison");
85 } 88 }
86 89
87 } 90 }
88 91
89 @ParameterizedTest(name = "Shared Store {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") 92 @ParameterizedTest(name = "Shared Store {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit " +
93 "frequency={4} seed={5} evil-hash={6}")
90 @MethodSource 94 @MethodSource
91 @Timeout(value = 10) 95 @Timeout(value = 10)
92 @Tag("smoke") 96 @Tag("smoke")
93 void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, 97 void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency,
94 boolean evilHash) { 98 int seed, boolean evilHash) {
95 runFuzzTest("SharedS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, 99 runFuzzTest("SharedS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues,
96 commitFrequency, evilHash); 100 nullDefault, commitFrequency, evilHash);
97 } 101 }
98 102
99 static Stream<Arguments> parametrizedFastFuzz() { 103 static Stream<Arguments> parametrizedFastFuzz() {
100 return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, 104 return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions,
101 new Object[] { 2, 3 }, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, 105 commitFrequencyOptions, randomSeedOptions, new Object[]{false, true});
102 new Object[] { false, true });
103 } 106 }
104 107
105 @ParameterizedTest(name = "Shared Store {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") 108 @ParameterizedTest(name = "Shared Store {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit " +
109 "frequency={4} seed={5} evil-hash={6}")
106 @MethodSource 110 @MethodSource
107 @Tag("smoke") 111 @Tag("smoke")
108 @Tag("slow") 112 @Tag("slow")
109 void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, 113 void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency,
110 boolean evilHash) { 114 int seed, boolean evilHash) {
111 runFuzzTest("SharedS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, 115 runFuzzTest("SharedS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues,
112 commitFrequency, evilHash); 116 nullDefault, commitFrequency, evilHash);
113 } 117 }
114 118
115 static Stream<Arguments> parametrizedSlowFuzz() { 119 static Stream<Arguments> parametrizedSlowFuzz() {
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SingleThreadFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SingleThreadFuzzTest.java
new file mode 100644
index 00000000..1337cf3a
--- /dev/null
+++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SingleThreadFuzzTest.java
@@ -0,0 +1,66 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.tests.fuzz;
7
8import org.junit.jupiter.api.Tag;
9import org.junit.jupiter.api.Timeout;
10import org.junit.jupiter.params.ParameterizedTest;
11import org.junit.jupiter.params.provider.Arguments;
12import org.junit.jupiter.params.provider.MethodSource;
13import tools.refinery.store.map.VersionedMapStore;
14import tools.refinery.store.map.VersionedMapStoreFactoryBuilder;
15import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils;
16import tools.refinery.store.map.tests.utils.MapTestEnvironment;
17
18import java.util.stream.Stream;
19
20import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*;
21
22class SingleThreadFuzzTest {
23 private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, int commitFrequency, VersionedMapStoreFactoryBuilder<Integer, String> builder) {
24 String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault);
25
26 VersionedMapStore<Integer, String> store = builder.defaultValue(values[0]).build().createOne();
27
28 // initialize runnables
29 MultiThreadTestRunnable runnable = new MultiThreadTestRunnable(scenario, store, steps, maxKey, values, seed, commitFrequency);
30
31 // start threads;
32 runnable.run();
33 }
34
35 static final String title = "SingleThread {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} commit " +
36 "frequency={5} seed={6} config={7}";
37
38 @ParameterizedTest(name = title)
39 @MethodSource
40 @Timeout(value = 10)
41 @Tag("fuzz")
42 void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean defaultNull,
43 int commitFrequency, int seed, VersionedMapStoreFactoryBuilder<Integer, String> builder) {
44 runFuzzTest("SingleThreadS" + steps + "K" + noKeys + "V" + noValues + defaultNull + "CF" + commitFrequency +
45 "s" + seed, seed, steps, noKeys, noValues, defaultNull, commitFrequency, builder);
46 }
47
48 static Stream<Arguments> parametrizedFastFuzz() {
49 return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions,
50 new Object[]{10, 100}, randomSeedOptions, storeConfigs);
51 }
52
53 @ParameterizedTest(name = title)
54 @MethodSource
55 @Tag("fuzz")
56 @Tag("slow")
57 void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault,
58 int commitFrequency, int seed, VersionedMapStoreFactoryBuilder<Integer, String> builder) {
59 runFuzzTest("SingleThreadS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues,
60 nullDefault, commitFrequency, builder);
61 }
62
63 static Stream<Arguments> parametrizedSlowFuzz() {
64 return FuzzTestUtils.changeStepCount(RestoreFuzzTest.parametrizedFastFuzz(), 1);
65 }
66}
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java
new file mode 100644
index 00000000..ec04904e
--- /dev/null
+++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java
@@ -0,0 +1,53 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.tests.fuzz.utils;
7
8import tools.refinery.store.map.VersionedMapStore;
9import tools.refinery.store.map.VersionedMapStoreFactoryBuilder;
10import tools.refinery.store.map.tests.utils.MapTestEnvironment;
11
12public final class FuzzTestCollections {
13 public static final Object[] stepCounts = {FuzzTestUtils.FAST_STEP_COUNT};
14 public static final Object[] keyCounts = {1 , 32, 32 * 32};
15 public static final Object[] valueCounts = {2, 3};
16 public static final Object[] nullDefaultOptions = {false, true};
17 public static final Object[] commitFrequencyOptions = {1, 10, 100};
18 public static final Object[] randomSeedOptions = {1};
19 public static final Object[] storeConfigs = {
20 // State based
21 // Default
22 VersionedMapStore.<Integer,String>builder()
23 .stateBasedHashProvider(MapTestEnvironment.prepareHashProvider(false))
24 .stateBasedSharingStrategy(VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE),
25 // Evil hash code test
26 VersionedMapStore.<Integer,String>builder()
27 .stateBasedHashProvider(MapTestEnvironment.prepareHashProvider(true))
28 .stateBasedSharingStrategy(VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE),
29 // No weak hashmap test
30 VersionedMapStore.<Integer,String>builder()
31 .versionFreeing(false)
32 .stateBasedHashProvider(MapTestEnvironment.prepareHashProvider(false))
33 .stateBasedSharingStrategy(VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE),
34 // Copy when committing, do not hurt the work copy, share between saves.
35 VersionedMapStore.<Integer,String>builder()
36 .stateBasedImmutableWhenCommitting(false)
37 .stateBasedHashProvider(MapTestEnvironment.prepareHashProvider(false))
38 .stateBasedSharingStrategy(VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE),
39 // Copy when committing, do not hurt the work copy, do not share between states.
40 VersionedMapStore.<Integer,String>builder()
41 .stateBasedImmutableWhenCommitting(false)
42 .stateBasedHashProvider(MapTestEnvironment.prepareHashProvider(false))
43 .stateBasedSharingStrategy(VersionedMapStoreFactoryBuilder.SharingStrategy.NO_NODE_CACHE),
44
45 // Delta based
46 // Set based transactions
47 VersionedMapStore.<Integer,String>builder()
48 .deltaTransactionStrategy(VersionedMapStoreFactoryBuilder.DeltaTransactionStrategy.SET),
49 // List based transactions
50 VersionedMapStore.<Integer,String>builder()
51 .deltaTransactionStrategy(VersionedMapStoreFactoryBuilder.DeltaTransactionStrategy.LIST)
52 };
53}
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtils.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtils.java
index a819d348..32675635 100644
--- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtils.java
+++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtils.java
@@ -13,7 +13,7 @@ import java.util.stream.Stream;
13import org.junit.jupiter.params.provider.Arguments; 13import org.junit.jupiter.params.provider.Arguments;
14 14
15public final class FuzzTestUtils { 15public final class FuzzTestUtils {
16 public static final int FAST_STEP_COUNT = 500; 16 public static final int FAST_STEP_COUNT = 250;
17 public static final int SLOW_STEP_COUNT = 32 * 32 * 32 * 32; 17 public static final int SLOW_STEP_COUNT = 32 * 32 * 32 * 32;
18 18
19 private FuzzTestUtils() { 19 private FuzzTestUtils() {
@@ -56,14 +56,12 @@ public final class FuzzTestUtils {
56 56
57 public static Stream<Arguments> permutationWithSize(Object[]... valueOption) { 57 public static Stream<Arguments> permutationWithSize(Object[]... valueOption) {
58 int size = 1; 58 int size = 1;
59 for (int i = 0; i < valueOption.length; i++) { 59 for (Object[] objects : valueOption) {
60 size *= valueOption[i].length; 60 size *= objects.length;
61 } 61 }
62 Object[][] newValueOption = new Object[valueOption.length + 1][]; 62 Object[][] newValueOption = new Object[valueOption.length + 1][];
63 newValueOption[0] = new Object[] { size }; 63 newValueOption[0] = new Object[]{size};
64 for (int i = 1; i < newValueOption.length; i++) { 64 System.arraycopy(valueOption, 0, newValueOption, 1, newValueOption.length - 1);
65 newValueOption[i] = valueOption[i - 1];
66 }
67 return permutation(newValueOption); 65 return permutation(newValueOption);
68 } 66 }
69} 67}
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtilsTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtilsTest.java
index dc621574..951d6336 100644
--- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtilsTest.java
+++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtilsTest.java
@@ -6,31 +6,36 @@
6package tools.refinery.store.map.tests.fuzz.utils; 6package tools.refinery.store.map.tests.fuzz.utils;
7 7
8import static org.junit.jupiter.api.Assertions.assertEquals; 8import static org.junit.jupiter.api.Assertions.assertEquals;
9import static org.junit.jupiter.api.Assertions.assertTrue;
9 10
10import java.util.List; 11import java.util.List;
12import java.util.Optional;
11 13
12import org.junit.jupiter.api.Test; 14import org.junit.jupiter.api.Test;
15import org.junit.jupiter.params.provider.Arguments;
13 16
14class FuzzTestUtilsTest { 17class FuzzTestUtilsTest {
15 @Test 18 @Test
16 void permutationInternalTest() { 19 void permutationInternalTest() {
17 List<List<Object>> res = FuzzTestUtils.permutationInternal(0, new Object[] { 1, 2, 3 }, 20 List<List<Object>> res = FuzzTestUtils.permutationInternal(0, new Object[]{1, 2, 3},
18 new Object[] { 'a', 'b', 'c' }, new Object[] { "alpha", "beta", "gamma", "delta" }); 21 new Object[]{'a', 'b', 'c'}, new Object[]{"alpha", "beta", "gamma", "delta"});
19 assertEquals(3 * 3 * 4, res.size()); 22 assertEquals(3 * 3 * 4, res.size());
20 } 23 }
21 24
22 @Test 25 @Test
23 void permutationTest1() { 26 void permutationTest1() {
24 var res = FuzzTestUtils.permutation(new Object[] { 1, 2, 3 }, new Object[] { 'a', 'b', 'c' }, 27 var res = FuzzTestUtils.permutation(new Object[]{1, 2, 3}, new Object[]{'a', 'b', 'c'},
25 new Object[] { "alpha", "beta", "gamma", "delta" }); 28 new Object[]{"alpha", "beta", "gamma", "delta"});
26 assertEquals(3 * 3 * 4, res.count()); 29 assertEquals(3 * 3 * 4, res.count());
27 } 30 }
28 31
29 @Test 32 @Test
30 void permutationTest2() { 33 void permutationTest2() {
31 var res = FuzzTestUtils.permutation(new Object[] { 1, 2, 3 }, new Object[] { 'a', 'b', 'c' }, 34 var res = FuzzTestUtils.permutation(new Object[]{1, 2, 3}, new Object[]{'a', 'b', 'c'},
32 new Object[] { "alpha", "beta", "gamma", "delta" }); 35 new Object[]{"alpha", "beta", "gamma", "delta"});
33 var arguments = res.findFirst().get().get(); 36 Optional<Arguments> first = res.findFirst();
37 assertTrue(first.isPresent());
38 var arguments = first.get().get();
34 assertEquals(1, arguments[0]); 39 assertEquals(1, arguments[0]);
35 assertEquals('a', arguments[1]); 40 assertEquals('a', arguments[1]);
36 assertEquals("alpha", arguments[2]); 41 assertEquals("alpha", arguments[2]);
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java
index f861f496..b84df280 100644
--- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java
+++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java
@@ -6,7 +6,7 @@
6package tools.refinery.store.map.tests.utils; 6package tools.refinery.store.map.tests.utils;
7 7
8import tools.refinery.store.map.*; 8import tools.refinery.store.map.*;
9import tools.refinery.store.map.internal.VersionedMapImpl; 9import tools.refinery.store.map.internal.state.VersionedMapStateImpl;
10 10
11import java.util.*; 11import java.util.*;
12import java.util.Map.Entry; 12import java.util.Map.Entry;
@@ -14,35 +14,35 @@ import java.util.Map.Entry;
14import static org.junit.jupiter.api.Assertions.*; 14import static org.junit.jupiter.api.Assertions.*;
15 15
16public class MapTestEnvironment<K, V> { 16public class MapTestEnvironment<K, V> {
17 public static String[] prepareValues(int maxValue) { 17 public static String[] prepareValues(int maxValue, boolean nullDefault) {
18 String[] values = new String[maxValue]; 18 String[] values = new String[maxValue];
19 values[0] = "DEFAULT"; 19 if (nullDefault) {
20 values[0] = null;
21 } else {
22 values[0] = "DEFAULT";
23 }
24
20 for (int i = 1; i < values.length; i++) { 25 for (int i = 1; i < values.length; i++) {
21 values[i] = "VAL" + i; 26 values[i] = "VAL" + i;
22 } 27 }
23 return values; 28 return values;
24 } 29 }
25 30
26 public static ContinousHashProvider<Integer> prepareHashProvider(final boolean evil) { 31 public static ContinuousHashProvider<Integer> prepareHashProvider(final boolean evil) {
27 // Use maxPrime = 2147483629 32 // Use maxPrime = 2147483629
28 33
29 ContinousHashProvider<Integer> chp = new ContinousHashProvider<Integer>() { 34 return (key, index) -> {
30 35 if (evil && index < 15 && index < key / 3) {
31 @Override 36 return 7;
32 public int getHash(Integer key, int index) { 37 }
33 if (evil && index < 15 && index < key / 3) { 38 int result = 1;
34 return 7; 39 final int prime = 31;
35 }
36 int result = 1;
37 final int prime = 31;
38 40
39 result = prime * result + key; 41 result = prime * result + key;
40 result = prime * result + index; 42 result = prime * result + index;
41 43
42 return result; 44 return result;
43 }
44 }; 45 };
45 return chp;
46 } 46 }
47 47
48 public static void printStatus(String scenario, int actual, int max, String stepName) { 48 public static void printStatus(String scenario, int actual, int max, String stepName) {
@@ -60,29 +60,17 @@ public class MapTestEnvironment<K, V> {
60 60
61 public static <K, V> void compareTwoMaps(String title, VersionedMap<K, V> map1, 61 public static <K, V> void compareTwoMaps(String title, VersionedMap<K, V> map1,
62 VersionedMap<K, V> map2, List<Throwable> errors) { 62 VersionedMap<K, V> map2, List<Throwable> errors) {
63 assertEqualsList(map1.getSize(), map2.getSize(), title + ": Sizes not equal", errors); 63 map1.checkIntegrity();
64 map2.checkIntegrity();
64 65
65 Cursor<K, V> cursor1 = map1.getAll(); 66 assertContentEqualsList(map1, map2, title + ": map1.contentEquals(map2)", errors);
66 Cursor<K, V> cursor2 = map2.getAll(); 67 assertContentEqualsList(map2, map1, title + ": map2.contentEquals(map1)", errors);
67 while (!cursor1.isTerminated()) { 68 assertEqualsList(map1.getSize(), map2.getSize(), title + ": Sizes not equal", errors);
68 if (cursor2.isTerminated()) {
69 fail("cursor 2 terminated before cursor1");
70 }
71 assertEqualsList(cursor1.getKey(), cursor2.getKey(), title + ": Keys not equal", errors);
72 assertEqualsList(cursor2.getValue(), cursor2.getValue(), title + ": Values not equal", errors);
73 cursor1.move();
74 cursor2.move();
75 }
76 if (!cursor2.isTerminated()) {
77 fail("cursor 1 terminated before cursor 2");
78 }
79 69
80 for (var mode : ContentHashCode.values()) { 70 for (var mode : ContentHashCode.values()) {
81 assertEqualsList(map1.contentHashCode(mode), map2.contentHashCode(mode), 71 assertEqualsList(map1.contentHashCode(mode), map2.contentHashCode(mode),
82 title + ": " + mode + " hashCode check", errors); 72 title + ": " + mode + " hashCode check", errors);
83 } 73 }
84 assertContentEqualsList(map1, map2, title + ": map1.contentEquals(map2)", errors);
85 assertContentEqualsList(map2, map1, title + ": map2.contentEquals(map1)", errors);
86 } 74 }
87 75
88 private static void assertEqualsList(Object o1, Object o2, String message, List<Throwable> errors) { 76 private static void assertEqualsList(Object o1, Object o2, String message, List<Throwable> errors) {
@@ -112,29 +100,35 @@ public class MapTestEnvironment<K, V> {
112 } 100 }
113 } 101 }
114 102
115 public VersionedMapImpl<K, V> sut; 103 final private VersionedMap<K, V> sut;
116 Map<K, V> oracle = new HashMap<K, V>(); 104 final private V defaultValue;
105 Map<K, V> oracle = new HashMap<>();
117 106
118 public MapTestEnvironment(VersionedMapImpl<K, V> sut) { 107 public MapTestEnvironment(VersionedMap<K, V> sut) {
119 this.sut = sut; 108 this.sut = sut;
109 this.defaultValue = sut.getDefaultValue();
120 } 110 }
121 111
122 public void put(K key, V value) { 112 public void put(K key, V value) {
123 V oldSutValue = sut.put(key, value); 113 V oldSutValue = sut.put(key, value);
124 V oldOracleValue; 114 V oldOracleValue;
125 if (value != sut.getDefaultValue()) { 115 if (value != defaultValue) {
126 oldOracleValue = oracle.put(key, value); 116 oldOracleValue = oracle.put(key, value);
127 } else { 117 } else {
128 oldOracleValue = oracle.remove(key); 118 oldOracleValue = oracle.remove(key);
129 } 119 }
130 if (oldSutValue == sut.getDefaultValue() && oldOracleValue != null) { 120 if (oldSutValue == defaultValue && oldOracleValue != null) {
131 fail("After put, SUT old nodeId was default, but oracle old value was " + oldOracleValue); 121 fail("After put, SUT old nodeId was default, but oracle old value was " + oldOracleValue);
132 } 122 }
133 if (oldSutValue != sut.getDefaultValue()) { 123 if (oldSutValue != defaultValue) {
134 assertEquals(oldOracleValue, oldSutValue); 124 assertEquals(oldOracleValue, oldSutValue);
135 } 125 }
136 } 126 }
137 127
128 public Version commit(){
129 return sut.commit();
130 }
131
138 public void checkEquivalence(String title) { 132 public void checkEquivalence(String title) {
139 // 0. Checking integrity 133 // 0. Checking integrity
140 try { 134 try {
@@ -181,7 +175,7 @@ public class MapTestEnvironment<K, V> {
181 long sutSize = sut.getSize(); 175 long sutSize = sut.getSize();
182 if (oracleSize != sutSize || oracleSize != elementsInSutEntrySet) { 176 if (oracleSize != sutSize || oracleSize != elementsInSutEntrySet) {
183 printComparison(); 177 printComparison();
184 fail(title + ": Non-equivalent size() result: SUT.getSize()=" + sutSize + ", SUT.entryset.size=" 178 fail(title + ": Non-equivalent size() result: SUT.getSize()=" + sutSize + ", SUT.entrySet.size="
185 + elementsInSutEntrySet + ", Oracle=" + oracleSize + "!"); 179 + elementsInSutEntrySet + ", Oracle=" + oracleSize + "!");
186 } 180 }
187 } 181 }
@@ -190,15 +184,15 @@ public class MapTestEnvironment<K, V> {
190 K previous = null; 184 K previous = null;
191 Cursor<K, V> cursor = versionedMap.getAll(); 185 Cursor<K, V> cursor = versionedMap.getAll();
192 while (cursor.move()) { 186 while (cursor.move()) {
193 System.out.println(cursor.getKey() + " " + ((VersionedMapImpl<K, V>) versionedMap).getHashProvider().getHash(cursor.getKey(), 0)); 187 //System.out.println(cursor.getKey() + " " + ((VersionedMapImpl<K, V>) versionedMap).getHashProvider()
188 // .getHash(cursor.getKey(), 0));
194 if (previous != null) { 189 if (previous != null) {
195 int comparisonResult = ((VersionedMapImpl<K, V>) versionedMap).getHashProvider().compare(previous, 190 int comparisonResult = ((VersionedMapStateImpl<K, V>) versionedMap).getHashProvider().compare(previous,
196 cursor.getKey()); 191 cursor.getKey());
197 assertTrue(comparisonResult < 0, scenario + " Cursor order is not incremental!"); 192 assertTrue(comparisonResult < 0, scenario + " Cursor order is not incremental!");
198 } 193 }
199 previous = cursor.getKey(); 194 previous = cursor.getKey();
200 } 195 }
201 System.out.println();
202 } 196 }
203 197
204 public void printComparison() { 198 public void printComparison() {
diff --git a/subprojects/store/src/test/java/tools/refinery/store/model/hashtests/HashEfficiencyTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/hashtests/HashEfficiencyTest.java
index 4d4f5e26..5b595da7 100644
--- a/subprojects/store/src/test/java/tools/refinery/store/model/hashtests/HashEfficiencyTest.java
+++ b/subprojects/store/src/test/java/tools/refinery/store/model/hashtests/HashEfficiencyTest.java
@@ -3,7 +3,7 @@
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.store.model.hashtests; 6package tools.refinery.store.model.hashTests;
7 7
8import static org.junit.jupiter.api.Assertions.assertEquals; 8import static org.junit.jupiter.api.Assertions.assertEquals;
9 9
@@ -14,10 +14,9 @@ import java.util.Random;
14 14
15import org.junit.jupiter.api.Test; 15import org.junit.jupiter.api.Test;
16 16
17import tools.refinery.store.map.ContinousHashProvider; 17import tools.refinery.store.map.ContinuousHashProvider;
18import tools.refinery.store.tuple.Tuple; 18import tools.refinery.store.tuple.Tuple;
19import tools.refinery.store.model.TupleHashProvider; 19import tools.refinery.store.model.TupleHashProvider;
20import tools.refinery.store.model.TupleHashProviderBitMagic;
21 20
22class HashEfficiencyTest { 21class HashEfficiencyTest {
23 22
@@ -95,7 +94,7 @@ class HashEfficiencyTest {
95 List<Tuple> p = nRandoms(2, amount, 1);; 94 List<Tuple> p = nRandoms(2, amount, 1);;
96 assertEquals(amount,p.size()); 95 assertEquals(amount,p.size());
97 } 96 }
98 private static double calculateHashClashes(List<Tuple> tuples, ContinousHashProvider<Tuple> chp) { 97 private static double calculateHashClashes(List<Tuple> tuples, ContinuousHashProvider<Tuple> chp) {
99 int sumClashes = 0; 98 int sumClashes = 0;
100 99
101 for(int i = 0; i<tuples.size(); i++) { 100 for(int i = 0; i<tuples.size(); i++) {
@@ -108,7 +107,7 @@ class HashEfficiencyTest {
108 } 107 }
109 return (sumClashes+0.0) / tuples.size(); 108 return (sumClashes+0.0) / tuples.size();
110 } 109 }
111 private static int calculateHashClash(ContinousHashProvider<Tuple> chp, Tuple a, Tuple b) { 110 private static int calculateHashClash(ContinuousHashProvider<Tuple> chp, Tuple a, Tuple b) {
112 if(a.equals(b)) return 0; 111 if(a.equals(b)) return 0;
113 final int bits = 5; 112 final int bits = 5;
114 final int segments = Integer.SIZE/bits; 113 final int segments = Integer.SIZE/bits;
@@ -131,11 +130,9 @@ class HashEfficiencyTest {
131 } 130 }
132 public static void main(String[] args) { 131 public static void main(String[] args) {
133 List<String> hashNames = new LinkedList<>(); 132 List<String> hashNames = new LinkedList<>();
134 List<ContinousHashProvider<Tuple>> hashes = new LinkedList<>(); 133 List<ContinuousHashProvider<Tuple>> hashes = new LinkedList<>();
135 hashNames.add("PrimeGroup"); 134 hashNames.add("PrimeGroup");
136 hashes.add(new TupleHashProvider()); 135 hashes.add(new TupleHashProvider());
137 hashNames.add("BitMagic");
138 hashes.add(new TupleHashProviderBitMagic());
139 136
140 int[] arities = new int[] {2,3,4,5}; 137 int[] arities = new int[] {2,3,4,5};
141 int[] sizes = new int[] {32*32,32*32*8}; 138 int[] sizes = new int[] {32*32,32*32*8};
diff --git a/subprojects/store/src/test/java/tools/refinery/store/model/tests/ModelTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/tests/ModelTest.java
index 56b75804..dc7b776e 100644
--- a/subprojects/store/src/test/java/tools/refinery/store/model/tests/ModelTest.java
+++ b/subprojects/store/src/test/java/tools/refinery/store/model/tests/ModelTest.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.model.tests; 6package tools.refinery.store.model.tests;
7 7
8import org.junit.jupiter.api.Test; 8import org.junit.jupiter.api.Test;
9import tools.refinery.store.map.Version;
9import tools.refinery.store.model.Model; 10import tools.refinery.store.model.Model;
10import tools.refinery.store.model.ModelStore; 11import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.representation.Symbol; 12import tools.refinery.store.representation.Symbol;
@@ -120,7 +121,7 @@ class ModelTest {
120 assertTrue(model.hasUncommittedChanges()); 121 assertTrue(model.hasUncommittedChanges());
121 assertEquals(Model.NO_STATE_ID, model.getState()); 122 assertEquals(Model.NO_STATE_ID, model.getState());
122 123
123 long state1 = model.commit(); 124 Version state1 = model.commit();
124 125
125 assertFalse(model.hasUncommittedChanges()); 126 assertFalse(model.hasUncommittedChanges());
126 assertEquals(state1, model.getState()); 127 assertEquals(state1, model.getState());
@@ -134,7 +135,7 @@ class ModelTest {
134 assertTrue(model.hasUncommittedChanges()); 135 assertTrue(model.hasUncommittedChanges());
135 assertEquals(state1, model.getState()); 136 assertEquals(state1, model.getState());
136 137
137 long state2 = model.commit(); 138 Version state2 = model.commit();
138 139
139 assertFalse(model.hasUncommittedChanges()); 140 assertFalse(model.hasUncommittedChanges());
140 assertEquals(state2, model.getState()); 141 assertEquals(state2, model.getState());
diff --git a/subprojects/store/src/test/java/tools/refinery/store/statecoding/EquivalenceTest.java b/subprojects/store/src/test/java/tools/refinery/store/statecoding/EquivalenceTest.java
new file mode 100644
index 00000000..3c35849e
--- /dev/null
+++ b/subprojects/store/src/test/java/tools/refinery/store/statecoding/EquivalenceTest.java
@@ -0,0 +1,217 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.map.Version;
10import tools.refinery.store.model.Model;
11import tools.refinery.store.model.ModelStore;
12import tools.refinery.store.representation.Symbol;
13import tools.refinery.store.statecoding.neighbourhood.ObjectCodeImpl;
14import tools.refinery.store.tuple.Tuple;
15
16import static org.junit.jupiter.api.Assertions.assertEquals;
17
18class EquivalenceTest {
19 Symbol<Boolean> person = new Symbol<>("Person", 1, Boolean.class, false);
20 Symbol<Integer> age = new Symbol<>("age", 1, Integer.class, null);
21 Symbol<Boolean> friend = new Symbol<>("friend", 2, Boolean.class, false);
22 Symbol<Boolean> parents = new Symbol<>("parents", 3, Boolean.class, false);
23 Symbol<Integer> population = new Symbol<>("population", 0, Integer.class, 0);
24
25 private ModelStore createStore() {
26 return ModelStore.builder()
27 .symbols(person, age, friend, parents, population)
28 .with(StateCoderAdapter.builder())
29 .build();
30 }
31
32 @Test
33 void emptyModelCode0() {
34 ModelStore store = createStore();
35 var stateCoder = store.getAdapter(StateCoderStoreAdapter.class);
36 Model model = createStore().createEmptyModel();
37 Version v1 = model.commit();
38 Version v2 = model.commit();
39
40 assertEquals(StateEquivalenceChecker.EquivalenceResult.ISOMORPHIC, stateCoder.checkEquivalence(v1, v2));
41
42 var personI = model.getInterpretation(person);
43 var friendI = model.getInterpretation(friend);
44
45 personI.put(Tuple.of(1), true);
46 personI.put(Tuple.of(2), true);
47 friendI.put(Tuple.of(1, 2), true);
48
49 Version v3 = model.commit();
50
51 assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v1, v3));
52 }
53
54 @Test
55 void nullRelationTest() {
56 ModelStore store = createStore();
57 var stateCoder = store.getAdapter(StateCoderStoreAdapter.class);
58 Model model = createStore().createEmptyModel();
59
60 var populationI = model.getInterpretation(population);
61
62 Version v1 = model.commit();
63
64 populationI.put(Tuple.of(), 1);
65 Version v2 = model.commit();
66
67 assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v1, v2));
68
69 populationI.put(Tuple.of(), 2);
70 Version v3 = model.commit();
71
72 assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v2, v3));
73 }
74
75 @Test
76 void unaryBooleanTest() {
77 ModelStore store = createStore();
78 var stateCoder = store.getAdapter(StateCoderStoreAdapter.class);
79 Model model = createStore().createEmptyModel();
80
81 var personI = model.getInterpretation(person);
82
83 Version v1 = model.commit();
84
85 personI.put(Tuple.of(1), true);
86 Version v2 = model.commit();
87
88 assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v1, v2));
89
90 personI.put(Tuple.of(2), true);
91 Version v3 = model.commit();
92
93 assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v2, v3));
94
95 personI.put(Tuple.of(1), false);
96 Version v4 = model.commit();
97
98 assertEquals(StateEquivalenceChecker.EquivalenceResult.ISOMORPHIC, stateCoder.checkEquivalence(v2, v4));
99 }
100
101 @Test
102 void unaryIntTest() {
103 ModelStore store = createStore();
104 var stateCoder = store.getAdapter(StateCoderStoreAdapter.class);
105 Model model = createStore().createEmptyModel();
106
107 var ageI = model.getInterpretation(age);
108
109 ageI.put(Tuple.of(1), 3);
110 Version v1 = model.commit();
111
112 ageI.put(Tuple.of(1), 4);
113 Version v2 = model.commit();
114
115 assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v1, v2));
116
117 ageI.put(Tuple.of(2), 4);
118 Version v3 = model.commit();
119
120 assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v2, v3));
121
122 ageI.put(Tuple.of(1), null);
123 Version v4 = model.commit();
124
125 assertEquals(StateEquivalenceChecker.EquivalenceResult.ISOMORPHIC, stateCoder.checkEquivalence(v2, v4));
126 }
127
128 @Test
129 void binaryTest() {
130 ModelStore store = createStore();
131 var stateCoder = store.getAdapter(StateCoderStoreAdapter.class);
132 Model model = createStore().createEmptyModel();
133
134 var friendI = model.getInterpretation(friend);
135
136 Version v1 = model.commit();
137
138 friendI.put(Tuple.of(1, 2), true);
139 Version v2 = model.commit();
140
141 assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v1, v2));
142
143 friendI.put(Tuple.of(2, 1), true);
144 Version v3 = model.commit();
145
146 assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v2, v3));
147
148 friendI.put(Tuple.of(1, 2), false);
149 Version v4 = model.commit();
150
151 assertEquals(StateEquivalenceChecker.EquivalenceResult.ISOMORPHIC, stateCoder.checkEquivalence(v2, v4));
152 }
153
154 @Test
155 void NaryTest() {
156 ModelStore store = createStore();
157 var stateCoder = store.getAdapter(StateCoderStoreAdapter.class);
158 Model model = createStore().createEmptyModel();
159
160 var parentsI = model.getInterpretation(parents);
161
162 Version v1 = model.commit();
163
164 parentsI.put(Tuple.of(3, 1, 2), true);
165 Version v2 = model.commit();
166
167 assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v1, v2));
168
169 parentsI.put(Tuple.of(4, 1, 2), true);
170 Version v3 = model.commit();
171
172 assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v2, v3));
173
174 parentsI.put(Tuple.of(3, 1, 2), false);
175 Version v4 = model.commit();
176
177 assertEquals(StateEquivalenceChecker.EquivalenceResult.ISOMORPHIC, stateCoder.checkEquivalence(v2, v4));
178 }
179
180 @Test
181 void largeUnknownTest() {
182 final int limit = 100;
183
184 StateCodeCalculator calculator = () -> {
185 var code = new ObjectCodeImpl();
186 for (int i = 0; i < limit; i++) {
187 code.set(i, 1);
188 }
189 return new StateCoderResult(1, code);
190 };
191
192 ModelStore store = ModelStore.builder()
193 .symbols(person, age, friend, parents, population)
194 .with(StateCoderAdapter.builder()
195 .stateCodeCalculatorFactory((p1, p2) -> calculator))
196 .build();
197
198 var stateCoder = store.getAdapter(StateCoderStoreAdapter.class);
199 Model model = createStore().createEmptyModel();
200
201 var personI = model.getInterpretation(person);
202 var friendI = model.getInterpretation(friend);
203
204 for (int i = 0; i < limit; i++) {
205 personI.put(Tuple.of(i), true);
206 }
207
208 friendI.put(Tuple.of(11,12),true);
209 var v1 = model.commit();
210
211 friendI.put(Tuple.of(11,12),false);
212 friendI.put(Tuple.of(21,22),false);
213 var v2 = model.commit();
214
215 assertEquals(StateEquivalenceChecker.EquivalenceResult.UNKNOWN, stateCoder.checkEquivalence(v1,v2));
216 }
217}
diff --git a/subprojects/store/src/test/java/tools/refinery/store/statecoding/ExperimentalSetupTest.java b/subprojects/store/src/test/java/tools/refinery/store/statecoding/ExperimentalSetupTest.java
new file mode 100644
index 00000000..f5ffc18d
--- /dev/null
+++ b/subprojects/store/src/test/java/tools/refinery/store/statecoding/ExperimentalSetupTest.java
@@ -0,0 +1,199 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8import org.eclipse.collections.api.factory.primitive.IntObjectMaps;
9import org.eclipse.collections.api.map.primitive.MutableIntObjectMap;
10import org.junit.jupiter.api.Tag;
11import org.junit.jupiter.api.Test;
12import org.junit.jupiter.params.ParameterizedTest;
13import org.junit.jupiter.params.provider.ValueSource;
14import tools.refinery.store.map.Version;
15import tools.refinery.store.model.Model;
16import tools.refinery.store.model.ModelStore;
17import tools.refinery.store.representation.Symbol;
18import tools.refinery.store.tuple.Tuple;
19
20import java.util.ArrayList;
21import java.util.HashSet;
22import java.util.List;
23import java.util.Set;
24
25import static org.junit.jupiter.api.Assertions.assertEquals;
26import static org.junit.jupiter.api.Assertions.assertTrue;
27
28class ExperimentalSetupTest {
29 static class ExperimentalSetupResult {
30 int versions = 0;
31 int different = 0;
32 int isomorphic = 0;
33 int unknown = 0;
34
35 double failureRatio() {
36 return (different + 0.0) / versions;
37 }
38
39 @Override
40 public String toString() {
41 return "ExperimentalSetupResult{" +
42 "versions=" + versions +
43 ", different=" + different +
44 ", isomorphic=" + isomorphic +
45 ", unknown=" + unknown +
46 ", ratio= " + failureRatio() +
47 '}';
48 }
49 }
50
51 static int MAX = 100000;
52
53 public static ExperimentalSetupResult generate(int size, boolean permuteTypes) {
54 Symbol<Boolean> person = new Symbol<>("Person", 1, Boolean.class, false);
55 Symbol<Boolean> friend = new Symbol<>("friend", 2, Boolean.class, false);
56
57 var store = ModelStore.builder()
58 .symbols(person, friend)
59 .with(StateCoderAdapter
60 .builder())
61 .build();
62
63 Set<Version> versions = new HashSet<>();
64 MutableIntObjectMap<List<Version>> codes = IntObjectMaps.mutable.empty();
65
66 var empty = store.createEmptyModel();
67 if (!permuteTypes) {
68 for (int i = 0; i < size; i++) {
69 empty.getInterpretation(person).put(Tuple.of(i), true);
70 }
71 }
72
73 var emptyVersion = empty.commit();
74 versions.add(emptyVersion);
75 var emptyCode = empty.getAdapter(StateCoderAdapter.class).calculateModelCode();
76 List<Version> emptyList = new ArrayList<>();
77 emptyList.add(emptyVersion);
78 codes.put(emptyCode, emptyList);
79
80 var storeAdapter = store.getAdapter(StateCoderStoreAdapter.class);
81 var result = new ExperimentalSetupResult();
82
83 int steps = 0;
84
85 if (permuteTypes) {
86 for (int i = 0; i < size; i++) {
87 var previousVersions = new HashSet<>(versions);
88 for (var version : previousVersions) {
89 var model = store.createModelForState(version);
90 model.getInterpretation(person).put(Tuple.of(i), true);
91
92 saveAsNewVersion(versions, codes, storeAdapter, result, model);
93
94 logProgress(steps++);
95 if (steps > MAX) {
96 result.versions = versions.size();
97 return result;
98 }
99 }
100 }
101 }
102
103 for (int i = 0; i < size; i++) {
104 for (int j = 0; j < size; j++) {
105 var previousVersions = new HashSet<>(versions);
106 for (var version : previousVersions) {
107
108 var model = store.createModelForState(version);
109 model.getInterpretation(friend).put(Tuple.of(i, j), true);
110
111 saveAsNewVersion(versions, codes, storeAdapter, result, model);
112
113 logProgress(steps++);
114 if (steps > MAX) {
115 result.versions = versions.size();
116 return result;
117 }
118 }
119 }
120 }
121
122 result.versions = versions.size();
123 return result;
124 }
125
126 private static void saveAsNewVersion(Set<Version> versions, MutableIntObjectMap<List<Version>> codes,
127 StateCoderStoreAdapter storeAdapter, ExperimentalSetupResult result,
128 Model model) {
129 Version version1 = model.commit();
130
131 var stateCode = model.getAdapter(StateCoderAdapter.class).calculateStateCode();
132 int code = stateCode.modelCode();
133 if (codes.containsKey(code)) {
134 Version similar = codes.get(code).get(0);
135
136 var outcome = storeAdapter.checkEquivalence(version1, similar);
137 if (outcome == StateEquivalenceChecker.EquivalenceResult.DIFFERENT) {
138 result.different++;
139 } else if (outcome == StateEquivalenceChecker.EquivalenceResult.UNKNOWN) {
140 result.unknown++;
141 } else {
142 result.isomorphic++;
143 }
144 } else {
145 versions.add(version1);
146
147 List<Version> newList = new ArrayList<>();
148 newList.add(version1);
149 codes.put(code, newList);
150 }
151 }
152
153 private static void logProgress(int steps) {
154 if (steps % 10000 == 0) {
155 System.out.println("Steps: " + steps + " / " + MAX);
156 }
157 }
158
159 static final double limit = 0.01;
160
161 @Test
162 void test0() {
163 assertEquals(1, generate(0, true).versions);
164 }
165
166 @ParameterizedTest
167 @ValueSource(ints = {1, 2, 3, 4})
168 void testForSmallUntypedModels(int size) {
169 var res = generate(size, false);
170 System.out.println(res);
171 assertTrue(res.failureRatio() < limit);
172 }
173
174 @ParameterizedTest
175 @ValueSource(ints = {1, 2, 3})
176 void testForSmallTypedModels(int size) {
177 var res = generate(size, true);
178 System.out.println(res);
179 assertTrue(res.failureRatio() < limit);
180 }
181
182 @Test
183 @Tag("fuzz")
184 @Tag("slow")
185 void testForLargeTypedModels() {
186 var res = generate(10, true);
187 System.out.println(res);
188 assertTrue(res.failureRatio() < limit);
189 }
190
191 @Test
192 @Tag("fuzz")
193 @Tag("slow")
194 void testForLargeUntypedModels() {
195 var res = generate(10, false);
196 System.out.println(res);
197 assertTrue(res.failureRatio() < limit);
198 }
199}
diff --git a/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderBuildTest.java b/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderBuildTest.java
new file mode 100644
index 00000000..0b738005
--- /dev/null
+++ b/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderBuildTest.java
@@ -0,0 +1,171 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.model.Interpretation;
10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.tuple.Tuple;
13
14import static org.junit.jupiter.api.Assertions.*;
15
16class StateCoderBuildTest {
17 Symbol<Boolean> person = new Symbol<>("Person", 1, Boolean.class, false);
18 Symbol<Integer> age = new Symbol<>("age", 1, Integer.class, null);
19 Symbol<Boolean> friend = new Symbol<>("friend", 2, Boolean.class, false);
20
21 @Test
22 void simpleStateCoderBuildTest() {
23 var store = ModelStore.builder()
24 .symbols(person, age, friend)
25 .with(StateCoderAdapter.builder())
26 .build();
27
28 var model = store.createEmptyModel();
29 var stateCoder = model.getAdapter(StateCoderAdapter.class);
30 assertNotNull(stateCoder);
31
32 var personI = model.getInterpretation(person);
33 var friendI = model.getInterpretation(friend);
34 var ageI = model.getInterpretation(age);
35
36 fill(personI, friendI, ageI);
37
38 stateCoder.calculateStateCode();
39 }
40
41 @Test
42 void excludeTest() {
43 var store = ModelStore.builder()
44 .symbols(person, age, friend)
45 .with(StateCoderAdapter.builder()
46 .exclude(person)
47 .exclude(age))
48 .build();
49
50 var model = store.createEmptyModel();
51 var stateCoder = model.getAdapter(StateCoderAdapter.class);
52 assertNotNull(stateCoder);
53
54 var personI = model.getInterpretation(person);
55 var friendI = model.getInterpretation(friend);
56 var ageI = model.getInterpretation(age);
57 fill(personI, friendI, ageI);
58
59 int code = stateCoder.calculateStateCode().modelCode();
60
61 ageI.put(Tuple.of(1), 3);
62 assertEquals(code, stateCoder.calculateStateCode().modelCode());
63
64 ageI.put(Tuple.of(1), null);
65 assertEquals(code, stateCoder.calculateStateCode().modelCode());
66
67 personI.put(Tuple.of(2), false);
68 assertEquals(code, stateCoder.calculateStateCode().modelCode());
69 }
70
71 @Test
72 void notIndividualTest() {
73 var store = ModelStore.builder()
74 .symbols(friend)
75 .with(StateCoderAdapter.builder())
76 .build();
77
78 var model = store.createEmptyModel();
79 var stateCoder = model.getAdapter(StateCoderAdapter.class);
80
81 var friendI = model.getInterpretation(friend);
82
83 friendI.put(Tuple.of(1, 2), true);
84 int code1 = stateCoder.calculateModelCode();
85
86 friendI.put(Tuple.of(1, 2), false);
87 friendI.put(Tuple.of(2, 1), true);
88 int code2 = stateCoder.calculateModelCode();
89
90 assertEquals(code1, code2);
91 }
92
93 @Test
94 void individualTest() {
95 var store = ModelStore.builder()
96 .symbols(friend)
97 .with(StateCoderAdapter.builder()
98 .individual(Tuple.of(1)))
99 .build();
100
101 var model = store.createEmptyModel();
102 var stateCoder = model.getAdapter(StateCoderAdapter.class);
103
104 var friendI = model.getInterpretation(friend);
105
106 friendI.put(Tuple.of(1, 2), true);
107 int code1 = stateCoder.calculateModelCode();
108
109 friendI.put(Tuple.of(1, 2), false);
110 friendI.put(Tuple.of(2, 1), true);
111 int code2 = stateCoder.calculateModelCode();
112
113 assertNotEquals(code1, code2);
114 }
115
116 @Test
117 void customStateCoderTest() {
118 final boolean[] called = new boolean[]{false};
119 StateCodeCalculator mock = () -> {
120 called[0] = true;
121 return null;
122 };
123
124 var store = ModelStore.builder()
125 .symbols(friend)
126 .with(StateCoderAdapter.builder()
127 .stateCodeCalculatorFactory((interpretations, individuals) -> mock))
128 .build();
129
130 var model = store.createEmptyModel();
131 var stateCoder = model.getAdapter(StateCoderAdapter.class);
132
133 stateCoder.calculateStateCode();
134
135 assertTrue(called[0]);
136 }
137
138 @Test
139 void customEquivalenceCheckerTest() {
140 final boolean[] called = new boolean[]{false};
141 StateEquivalenceChecker mock = (p1, p2, p3, p4, p5) -> {
142 called[0] = true;
143 return StateEquivalenceChecker.EquivalenceResult.UNKNOWN;
144 };
145
146 var store = ModelStore.builder()
147 .symbols(friend)
148 .with(StateCoderAdapter.builder()
149 .stateEquivalenceChecker(mock))
150 .build();
151
152 var model = store.createEmptyModel();
153 var v1 = model.commit();
154 var v2 = model.commit();
155
156 store.getAdapter(StateCoderStoreAdapter.class).checkEquivalence(v1, v2);
157
158 assertTrue(called[0]);
159 }
160
161
162 private static void fill(Interpretation<Boolean> personI, Interpretation<Boolean> friendI, Interpretation<Integer> ageI) {
163 personI.put(Tuple.of(1), true);
164 personI.put(Tuple.of(2), true);
165
166 ageI.put(Tuple.of(1), 5);
167 ageI.put(Tuple.of(2), 4);
168
169 friendI.put(Tuple.of(1, 2), true);
170 }
171}
diff --git a/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderUnitTest.java b/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderUnitTest.java
new file mode 100644
index 00000000..d94df841
--- /dev/null
+++ b/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderUnitTest.java
@@ -0,0 +1,195 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.tuple.Tuple;
13
14import java.util.Objects;
15
16import static org.junit.jupiter.api.Assertions.*;
17
18class StateCoderUnitTest {
19 Symbol<Boolean> person = new Symbol<>("Person", 1, Boolean.class, false);
20 Symbol<Integer> age = new Symbol<>("age", 1, Integer.class, null);
21 Symbol<Boolean> friend = new Symbol<>("friend", 2, Boolean.class, false);
22 Symbol<Boolean> parents = new Symbol<>("parents", 3, Boolean.class, false);
23 Symbol<Integer> population = new Symbol<>("population", 0, Integer.class, 0);
24
25 private Model createEmptyModel() {
26 var store = ModelStore.builder()
27 .symbols(person, age, friend, parents, population)
28 .with(StateCoderAdapter.builder())
29 .build();
30
31 return store.createEmptyModel();
32 }
33
34 @Test
35 void emptyModelCode0() {
36 Model model = createEmptyModel();
37 var stateCoder = model.getAdapter(StateCoderAdapter.class);
38
39 assertEquals(0, stateCoder.calculateModelCode());
40
41 var personI = model.getInterpretation(person);
42 var friendI = model.getInterpretation(friend);
43
44 personI.put(Tuple.of(1), true);
45 personI.put(Tuple.of(2), true);
46 friendI.put(Tuple.of(1, 2), true);
47
48 assertNotEquals(0, stateCoder.calculateModelCode());
49 }
50
51 @Test
52 void emptyObjectCode0() {
53 Model model = createEmptyModel();
54 var stateCoder = model.getAdapter(StateCoderAdapter.class);
55
56 var personI = model.getInterpretation(person);
57 var friendI = model.getInterpretation(friend);
58
59 assertEquals(0, stateCoder.calculateObjectCode().get(1));
60 assertEquals(0, stateCoder.calculateObjectCode().get(17));
61
62 personI.put(Tuple.of(1), true);
63 personI.put(Tuple.of(2), true);
64 friendI.put(Tuple.of(1, 2), true);
65
66 assertNotEquals(0, stateCoder.calculateObjectCode().get(1));
67 assertEquals(0, stateCoder.calculateObjectCode().get(17));
68 }
69
70 @Test
71 void nullRelationTest() {
72 Model model = createEmptyModel();
73 var stateCoder = model.getAdapter(StateCoderAdapter.class);
74
75 var populationI = model.getInterpretation(population);
76
77 final int hashOf0 = Objects.hashCode(0);
78
79 assertEquals(hashOf0, stateCoder.calculateModelCode());
80
81 populationI.put(Tuple.of(), 1);
82 int code1 = stateCoder.calculateModelCode();
83
84 assertNotEquals(hashOf0, stateCoder.calculateModelCode());
85
86 populationI.put(Tuple.of(), 2);
87 int code2 = stateCoder.calculateModelCode();
88
89 assertNotEquals(code1, code2);
90
91 populationI.put(Tuple.of(), 1);
92 assertEquals(code1, stateCoder.calculateModelCode());
93 }
94
95 @Test
96 void unaryBooleanTest() {
97 Model model = createEmptyModel();
98 var stateCoder = model.getAdapter(StateCoderAdapter.class);
99
100 var personI = model.getInterpretation(person);
101
102 assertEquals(0, stateCoder.calculateModelCode());
103
104 personI.put(Tuple.of(1), true);
105 int code1 = stateCoder.calculateModelCode();
106
107 assertNotEquals(0, stateCoder.calculateModelCode());
108
109 personI.put(Tuple.of(2), true);
110 int code2 = stateCoder.calculateModelCode();
111
112 assertNotEquals(code1, code2);
113
114 personI.put(Tuple.of(1), false);
115 assertEquals(code1, stateCoder.calculateModelCode());
116 }
117
118 @Test
119 void unaryIntTest() {
120 Model model = createEmptyModel();
121 var stateCoder = model.getAdapter(StateCoderAdapter.class);
122
123 var ageI = model.getInterpretation(age);
124
125 assertEquals(0, stateCoder.calculateModelCode());
126
127 ageI.put(Tuple.of(1), 4);
128 int code0 = stateCoder.calculateModelCode();
129
130 assertNotEquals(0, code0);
131
132 ageI.put(Tuple.of(1), 5);
133 int code1 = stateCoder.calculateModelCode();
134
135 assertNotEquals(code0, code1);
136
137 ageI.put(Tuple.of(2), 5);
138 int code2 = stateCoder.calculateModelCode();
139
140 assertNotEquals(code1, code2);
141
142 ageI.put(Tuple.of(1), null);
143 assertEquals(code1, stateCoder.calculateModelCode());
144 }
145
146 @Test
147 void binaryTest() {
148 Model model = createEmptyModel();
149 var stateCoder = model.getAdapter(StateCoderAdapter.class);
150
151 var friendI = model.getInterpretation(friend);
152
153 assertEquals(0, stateCoder.calculateModelCode());
154
155 friendI.put(Tuple.of(1, 2), true);
156 int code1 = stateCoder.calculateModelCode();
157
158 assertNotEquals(0, code1);
159
160 friendI.put(Tuple.of(2, 1), true);
161 int code2 = stateCoder.calculateModelCode();
162
163 assertNotEquals(code1, code2);
164
165 friendI.put(Tuple.of(1, 2), false);
166 int code3 = stateCoder.calculateModelCode();
167
168 assertEquals(code1, code3);
169 }
170
171 @Test
172 void NaryTest() {
173 Model model = createEmptyModel();
174 var stateCoder = model.getAdapter(StateCoderAdapter.class);
175
176 var parentsI = model.getInterpretation(parents);
177
178 assertEquals(0, stateCoder.calculateModelCode());
179
180 parentsI.put(Tuple.of(3, 1, 2), true);
181 int code1 = stateCoder.calculateModelCode();
182
183 assertNotEquals(0, code1);
184
185 parentsI.put(Tuple.of(4, 1, 2), true);
186 int code2 = stateCoder.calculateModelCode();
187
188 assertNotEquals(code1, code2);
189
190 parentsI.put(Tuple.of(3, 1, 2), false);
191 int code3 = stateCoder.calculateModelCode();
192
193 assertEquals(code1, code3);
194 }
195}
diff --git a/subprojects/visualization/build.gradle.kts b/subprojects/visualization/build.gradle.kts
new file mode 100644
index 00000000..abad0491
--- /dev/null
+++ b/subprojects/visualization/build.gradle.kts
@@ -0,0 +1,13 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7plugins {
8 id("tools.refinery.gradle.java-library")
9}
10
11dependencies {
12 api(project(":refinery-store-query"))
13}
diff --git a/subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerAdapter.java b/subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerAdapter.java
new file mode 100644
index 00000000..ae87d8ac
--- /dev/null
+++ b/subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerAdapter.java
@@ -0,0 +1,32 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.visualization;
7
8import tools.refinery.store.adapter.ModelAdapter;
9import tools.refinery.store.map.Version;
10import tools.refinery.store.tuple.Tuple;
11import tools.refinery.visualization.internal.ModelVisualizerBuilderImpl;
12
13import java.util.Collection;
14
15public interface ModelVisualizerAdapter extends ModelAdapter {
16
17 ModelVisualizerStoreAdapter getStoreAdapter();
18 static ModelVisualizerBuilder builder() {
19 return new ModelVisualizerBuilderImpl();
20 }
21
22 public void addTransition(Version from, Version to, String action);
23
24
25 public void addTransition(Version from, Version to, String action, Tuple activation);
26 public void addState(Version state);
27 public void addState(Version state, Collection<Double> fitness);
28 public void addState(Version state, String label);
29 public void addSolution(Version state);
30 public void visualize();
31
32}
diff --git a/subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerBuilder.java b/subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerBuilder.java
new file mode 100644
index 00000000..592f5fcf
--- /dev/null
+++ b/subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerBuilder.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.visualization;
7
8import tools.refinery.store.adapter.ModelAdapterBuilder;
9import tools.refinery.visualization.internal.FileFormat;
10
11public interface ModelVisualizerBuilder extends ModelAdapterBuilder {
12 ModelVisualizerBuilder withOutputpath(String outputpath);
13 ModelVisualizerBuilder withFormat(FileFormat format);
14 ModelVisualizerBuilder saveDesignSpace();
15 ModelVisualizerBuilder saveStates();
16}
diff --git a/subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerStoreAdapter.java b/subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerStoreAdapter.java
new file mode 100644
index 00000000..46663b2a
--- /dev/null
+++ b/subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerStoreAdapter.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.visualization;
7
8import tools.refinery.store.adapter.ModelStoreAdapter;
9import tools.refinery.visualization.internal.FileFormat;
10
11import java.util.Set;
12
13public interface ModelVisualizerStoreAdapter extends ModelStoreAdapter {
14
15 String getOutputPath();
16
17 boolean isRenderDesignSpace();
18
19 boolean isRenderStates();
20
21 Set<FileFormat> getFormats();
22}
diff --git a/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/FileFormat.java b/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/FileFormat.java
new file mode 100644
index 00000000..c5dffeb2
--- /dev/null
+++ b/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/FileFormat.java
@@ -0,0 +1,26 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.visualization.internal;
7
8public enum FileFormat {
9 BMP("bmp"),
10 DOT("dot"),
11 JPEG("jpg"),
12 PDF("pdf"),
13 PLAIN("plain"),
14 PNG("png"),
15 SVG("svg");
16
17 private final String format;
18
19 FileFormat(String format) {
20 this.format = format;
21 }
22
23 public String getFormat() {
24 return format;
25 }
26}
diff --git a/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizeStoreAdapterImpl.java b/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizeStoreAdapterImpl.java
new file mode 100644
index 00000000..04be22d6
--- /dev/null
+++ b/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizeStoreAdapterImpl.java
@@ -0,0 +1,60 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.visualization.internal;
7
8import tools.refinery.store.adapter.ModelAdapter;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.model.ModelStore;
11import tools.refinery.visualization.ModelVisualizerStoreAdapter;
12
13import java.util.Set;
14
15public class ModelVisualizeStoreAdapterImpl implements ModelVisualizerStoreAdapter {
16 private final ModelStore store;
17 private final String outputPath;
18 private final boolean renderDesignSpace;
19 private final boolean renderStates;
20 private final Set<FileFormat> formats;
21
22 public ModelVisualizeStoreAdapterImpl(ModelStore store, String outputPath, Set<FileFormat> formats,
23 boolean renderDesignSpace, boolean renderStates) {
24 this.store = store;
25 this.outputPath = outputPath;
26 this.formats = formats;
27 this.renderDesignSpace = renderDesignSpace;
28 this.renderStates = renderStates;
29 }
30
31 @Override
32 public ModelStore getStore() {
33 return store;
34 }
35
36 @Override
37 public ModelAdapter createModelAdapter(Model model) {
38 return new ModelVisualizerAdapterImpl(model, this);
39 }
40
41 @Override
42 public String getOutputPath() {
43 return outputPath;
44 }
45
46 @Override
47 public boolean isRenderDesignSpace() {
48 return renderDesignSpace;
49 }
50
51 @Override
52 public boolean isRenderStates() {
53 return renderStates;
54 }
55
56 @Override
57 public Set<FileFormat> getFormats() {
58 return formats;
59 }
60}
diff --git a/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerAdapterImpl.java b/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerAdapterImpl.java
new file mode 100644
index 00000000..531969b4
--- /dev/null
+++ b/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerAdapterImpl.java
@@ -0,0 +1,387 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.visualization.internal;
7
8import tools.refinery.store.map.Version;
9import tools.refinery.store.model.Interpretation;
10import tools.refinery.store.model.Model;
11import tools.refinery.store.representation.AnySymbol;
12import tools.refinery.store.representation.TruthValue;
13import tools.refinery.store.tuple.Tuple;
14import tools.refinery.visualization.ModelVisualizerAdapter;
15import tools.refinery.visualization.ModelVisualizerStoreAdapter;
16
17import java.io.*;
18import java.util.*;
19import java.util.stream.Collectors;
20
21public class ModelVisualizerAdapterImpl implements ModelVisualizerAdapter {
22 private final Model model;
23 private final ModelVisualizerStoreAdapter storeAdapter;
24 private final Map<AnySymbol, Interpretation<?>> allInterpretations;
25 private final StringBuilder designSpaceBuilder = new StringBuilder();
26 private final Map<Version, Integer> states = new HashMap<>();
27 private int transitionCounter = 0;
28 private Integer numberOfStates = 0;
29 private final String outputPath;
30 private final Set<FileFormat> formats;
31 private final boolean renderDesignSpace;
32 private final boolean renderStates;
33
34 private static final Map<Object, String> truthValueToDot = Map.of(
35 TruthValue.TRUE, "1",
36 TruthValue.FALSE, "0",
37 TruthValue.UNKNOWN, "½",
38 TruthValue.ERROR, "E",
39 true, "1",
40 false, "0"
41 );
42
43 public ModelVisualizerAdapterImpl(Model model, ModelVisualizerStoreAdapter storeAdapter) {
44 this.model = model;
45 this.storeAdapter = storeAdapter;
46 this.outputPath = storeAdapter.getOutputPath();
47 this.formats = storeAdapter.getFormats();
48 if (formats.isEmpty()) {
49 formats.add(FileFormat.SVG);
50 }
51 this.renderDesignSpace = storeAdapter.isRenderDesignSpace();
52 this.renderStates = storeAdapter.isRenderStates();
53
54 this.allInterpretations = new HashMap<>();
55 for (var symbol : storeAdapter.getStore().getSymbols()) {
56 var arity = symbol.arity();
57 if (arity < 1 || arity > 2) {
58 continue;
59 }
60 var interpretation = (Interpretation<?>) model.getInterpretation(symbol);
61 allInterpretations.put(symbol, interpretation);
62 }
63 designSpaceBuilder.append("digraph designSpace {\n");
64 designSpaceBuilder.append("""
65 nodesep=0
66 ranksep=5
67 node[
68 \tstyle=filled
69 \tfillcolor=white
70 ]
71 """);
72 }
73
74 @Override
75 public Model getModel() {
76 return model;
77 }
78
79 @Override
80 public ModelVisualizerStoreAdapter getStoreAdapter() {
81 return storeAdapter;
82 }
83
84 private String createDotForCurrentModelState() {
85
86 var unaryTupleToInterpretationsMap = new HashMap<Tuple, LinkedHashSet<Interpretation<?>>>();
87
88 var sb = new StringBuilder();
89
90 sb.append("digraph model {\n");
91 sb.append("""
92 node [
93 \tstyle="filled, rounded"
94 \tshape=plain
95 \tpencolor="#00000088"
96 \tfontname="Helvetica"
97 ]
98 """);
99 sb.append("""
100 edge [
101 \tlabeldistance=3
102 \tfontname="Helvetica"
103 ]
104 """);
105
106 for (var entry : allInterpretations.entrySet()) {
107 var key = entry.getKey();
108 var arity = key.arity();
109 var cursor = entry.getValue().getAll();
110 if (arity == 1) {
111 while (cursor.move()) {
112 unaryTupleToInterpretationsMap.computeIfAbsent(cursor.getKey(), k -> new LinkedHashSet<>())
113 .add(entry.getValue());
114 }
115 } else if (arity == 2) {
116 while (cursor.move()) {
117 var tuple = cursor.getKey();
118 for (var i = 0; i < tuple.getSize(); i++) {
119 var id = tuple.get(i);
120 unaryTupleToInterpretationsMap.computeIfAbsent(Tuple.of(id), k -> new LinkedHashSet<>());
121 }
122 sb.append(drawEdge(cursor.getKey(), key, entry.getValue()));
123 }
124 }
125 }
126 for (var entry : unaryTupleToInterpretationsMap.entrySet()) {
127 sb.append(drawElement(entry));
128 }
129 sb.append("}");
130 return sb.toString();
131 }
132
133 private StringBuilder drawElement(Map.Entry<Tuple, LinkedHashSet<Interpretation<?>>> entry) {
134 var sb = new StringBuilder();
135
136 var tableStyle = " CELLSPACING=\"0\" BORDER=\"2\" CELLBORDER=\"0\" CELLPADDING=\"4\" STYLE=\"ROUNDED\"";
137
138 var key = entry.getKey();
139 var id = key.get(0);
140 var mainLabel = String.valueOf(id);
141 var interpretations = entry.getValue();
142 var backgroundColor = toBackgroundColorString(averageColor(interpretations));
143
144 sb.append(id);
145 sb.append(" [\n");
146 sb.append("\tfillcolor=\"").append(backgroundColor).append("\"\n");
147 sb.append("\tlabel=");
148 if (interpretations.isEmpty()) {
149 sb.append("<<TABLE").append(tableStyle).append(">\n\t<TR><TD>").append(mainLabel).append("</TD></TR>");
150 }
151 else {
152 sb.append("<<TABLE").append(tableStyle).append(">\n\t\t<TR><TD COLSPAN=\"3\" BORDER=\"2\" SIDES=\"B\">")
153 .append(mainLabel).append("</TD></TR>\n");
154 for (var interpretation : interpretations) {
155 var rawValue = interpretation.get(key);
156
157 if (rawValue == null || rawValue.equals(TruthValue.FALSE) || rawValue.equals(false)) {
158 continue;
159 }
160 var color = "black";
161 if (rawValue.equals(TruthValue.ERROR)) {
162 color = "red";
163 }
164 var value = truthValueToDot.getOrDefault(rawValue, rawValue.toString());
165 var symbol = interpretation.getSymbol();
166
167 if (symbol.valueType() == String.class) {
168 value = "\"" + value + "\"";
169 }
170 sb.append("\t\t<TR><TD><FONT COLOR=\"").append(color).append("\">")
171 .append(interpretation.getSymbol().name())
172 .append("</FONT></TD><TD><FONT COLOR=\"").append(color).append("\">")
173 .append("=</FONT></TD><TD><FONT COLOR=\"").append(color).append("\">").append(value)
174 .append("</FONT></TD></TR>\n");
175 }
176 }
177 sb.append("\t\t</TABLE>>\n");
178 sb.append("]\n");
179
180 return sb;
181 }
182
183 private String drawEdge(Tuple edge, AnySymbol symbol, Interpretation<?> interpretation) {
184 var value = interpretation.get(edge);
185
186 if (value == null || value.equals(TruthValue.FALSE) || value.equals(false)) {
187 return "";
188 }
189
190 var sb = new StringBuilder();
191 var style = "solid";
192 var color = "black";
193 if (value.equals(TruthValue.UNKNOWN)) {
194 style = "dotted";
195 }
196 else if (value.equals(TruthValue.ERROR)) {
197 style = "dashed";
198 color = "red";
199 }
200
201 var from = edge.get(0);
202 var to = edge.get(1);
203 var name = symbol.name();
204 sb.append(from).append(" -> ").append(to)
205 .append(" [\n\tstyle=").append(style)
206 .append("\n\tcolor=").append(color)
207 .append("\n\tfontcolor=").append(color)
208 .append("\n\tlabel=\"").append(name)
209 .append("\"]\n");
210 return sb.toString();
211 }
212
213 private String toBackgroundColorString(Integer[] backgroundColor) {
214 if (backgroundColor.length == 3)
215 return String.format("#%02x%02x%02x", backgroundColor[0], backgroundColor[1], backgroundColor[2]);
216 else if (backgroundColor.length == 4)
217 return String.format("#%02x%02x%02x%02x", backgroundColor[0], backgroundColor[1], backgroundColor[2],
218 backgroundColor[3]);
219 return null;
220 }
221
222 private Integer[] typeColor(String name) {
223 var random = new Random(name.hashCode());
224 return new Integer[] { random.nextInt(128) + 128, random.nextInt(128) + 128, random.nextInt(128) + 128 };
225 }
226
227 private Integer[] averageColor(Set<Interpretation<?>> interpretations) {
228 if(interpretations.isEmpty()) {
229 return new Integer[]{256, 256, 256};
230 }
231 // TODO: Only use interpretations where the value is not false (or unknown)
232 var symbols = interpretations.stream()
233 .map(i -> typeColor(i.getSymbol().name())).toArray(Integer[][]::new);
234
235
236
237 return new Integer[] {
238 Arrays.stream(symbols).map(i -> i[0]).collect(Collectors.averagingInt(Integer::intValue)).intValue(),
239 Arrays.stream(symbols).map(i -> i[1]).collect(Collectors.averagingInt(Integer::intValue)).intValue(),
240 Arrays.stream(symbols).map(i -> i[2]).collect(Collectors.averagingInt(Integer::intValue)).intValue()
241 };
242 }
243
244 private String createDotForModelState(Version version) {
245 var currentVersion = model.getState();
246 model.restore(version);
247 var graph = createDotForCurrentModelState();
248 model.restore(currentVersion);
249 return graph;
250 }
251
252 private boolean saveDot(String dot, String filePath) {
253 File file = new File(filePath);
254 file.getParentFile().mkdirs();
255
256 try (FileWriter writer = new FileWriter(file)) {
257 writer.write(dot);
258 } catch (Exception e) {
259 e.printStackTrace();
260 return false;
261 }
262 return true;
263 }
264
265 private boolean renderDot(String dot, String filePath) {
266 return renderDot(dot, FileFormat.SVG, filePath);
267 }
268
269 private boolean renderDot(String dot, FileFormat format, String filePath) {
270 try {
271 Process process = new ProcessBuilder("dot", "-T" + format.getFormat(), "-o", filePath).start();
272
273 OutputStream osToProcess = process.getOutputStream();
274 PrintWriter pwToProcess = new PrintWriter(osToProcess);
275 pwToProcess.write(dot);
276 pwToProcess.close();
277 } catch (IOException e) {
278 e.printStackTrace();
279 return false;
280 }
281 return true;
282 }
283
284 @Override
285 public void addTransition(Version from, Version to, String action) {
286 designSpaceBuilder.append(states.get(from)).append(" -> ").append(states.get(to))
287 .append(" [label=\"").append(transitionCounter++).append(": ").append(action).append("\"]\n");
288 }
289
290 @Override
291 public void addTransition(Version from, Version to, String action, Tuple activation) {
292 designSpaceBuilder.append(states.get(from)).append(" -> ").append(states.get(to))
293 .append(" [label=\"").append(transitionCounter++).append(": ").append(action).append(" / ");
294
295
296 for (int i = 0; i < activation.getSize(); i++) {
297 designSpaceBuilder.append(activation.get(i));
298 if (i < activation.getSize() - 1) {
299 designSpaceBuilder.append(", ");
300 }
301 }
302 designSpaceBuilder.append("\"]\n");
303 }
304
305 @Override
306 public void addState(Version state) {
307 if (states.containsKey(state)) {
308 return;
309 }
310 states.put(state, numberOfStates++);
311 designSpaceBuilder.append(states.get(state)).append(" [URL=\"./").append(states.get(state)).append(".svg\"]\n");
312 }
313
314 @Override
315 public void addState(Version state, Collection<Double> fitness) {
316 var labelBuilder = new StringBuilder();
317 for (var f : fitness) {
318 labelBuilder.append(f).append(", ");
319 }
320 addState(state, labelBuilder.toString());
321 }
322
323 @Override
324 public void addState(Version state, String label) {
325 if (states.containsKey(state)) {
326 return;
327 }
328 states.put(state, numberOfStates++);
329 designSpaceBuilder.append(states.get(state)).append(" [label = \"").append(states.get(state)).append(" (");
330 designSpaceBuilder.append(label);
331 designSpaceBuilder.append(")\"\n").append("URL=\"./").append(states.get(state)).append(".svg\"]\n");
332 }
333
334 @Override
335 public void addSolution(Version state) {
336 addState(state);
337 designSpaceBuilder.append(states.get(state)).append(" [shape = doublecircle]\n");
338 }
339
340 private String buildDesignSpaceDot() {
341 designSpaceBuilder.append("}");
342 return designSpaceBuilder.toString();
343 }
344
345 private boolean saveDesignSpace(String path) {
346 saveDot(buildDesignSpaceDot(), path + "/designSpace.dot");
347 for (var entry : states.entrySet()) {
348 saveDot(createDotForModelState(entry.getKey()), path + "/" + entry.getValue() + ".dot");
349 }
350 return true;
351 }
352
353 private void renderDesignSpace(String path, Set<FileFormat> formats) {
354 File filePath = new File(path);
355 filePath.mkdirs();
356 if (renderStates) {
357 for (var entry : states.entrySet()) {
358 var stateId = entry.getValue();
359 var stateDot = createDotForModelState(entry.getKey());
360 for (var format : formats) {
361 if (format == FileFormat.DOT) {
362 saveDot(stateDot, path + "/" + stateId + ".dot");
363 }
364 else {
365 renderDot(stateDot, format, path + "/" + stateId + "." + format.getFormat());
366 }
367 }
368 }
369 }
370 if (renderDesignSpace) {
371 var designSpaceDot = buildDesignSpaceDot();
372 for (var format : formats) {
373 if (format == FileFormat.DOT) {
374 saveDot(designSpaceDot, path + "/designSpace.dot");
375 }
376 else {
377 renderDot(designSpaceDot, format, path + "/designSpace." + format.getFormat());
378 }
379 }
380 }
381 }
382
383 @Override
384 public void visualize() {
385 renderDesignSpace(outputPath, formats);
386 }
387}
diff --git a/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerBuilderImpl.java b/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerBuilderImpl.java
new file mode 100644
index 00000000..e4d801d8
--- /dev/null
+++ b/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerBuilderImpl.java
@@ -0,0 +1,55 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.visualization.internal;
7
8import tools.refinery.store.adapter.AbstractModelAdapterBuilder;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.visualization.ModelVisualizerBuilder;
11
12import java.util.LinkedHashSet;
13import java.util.Set;
14
15public class ModelVisualizerBuilderImpl
16 extends AbstractModelAdapterBuilder<ModelVisualizeStoreAdapterImpl>
17 implements ModelVisualizerBuilder {
18 private String outputPath;
19 private boolean saveDesignSpace = false;
20 private boolean saveStates = false;
21 private Set<FileFormat> formats = new LinkedHashSet<>();
22
23 @Override
24 protected ModelVisualizeStoreAdapterImpl doBuild(ModelStore store) {
25 return new ModelVisualizeStoreAdapterImpl(store, outputPath, formats, saveDesignSpace, saveStates);
26 }
27
28 @Override
29 public ModelVisualizerBuilder withOutputpath(String outputpath) {
30 checkNotConfigured();
31 this.outputPath = outputpath;
32 return this;
33 }
34
35 @Override
36 public ModelVisualizerBuilder withFormat(FileFormat format) {
37 checkNotConfigured();
38 this.formats.add(format);
39 return this;
40 }
41
42 @Override
43 public ModelVisualizerBuilder saveDesignSpace() {
44 checkNotConfigured();
45 this.saveDesignSpace = true;
46 return this;
47 }
48
49 @Override
50 public ModelVisualizerBuilder saveStates() {
51 checkNotConfigured();
52 this.saveStates = true;
53 return this;
54 }
55}