aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Oszkár Semeráth <semerath@mit.bme.hu>2023-08-04 14:01:37 +0200
committerLibravatar GitHub <noreply@github.com>2023-08-04 14:01:37 +0200
commit6fede3ca6dae0545083418c428e0797a01fa795c (patch)
tree31630f9a9cf093ee22ee73d25e51afcce9b9cdbb
parentMerge pull request #32 from nagilooh/design-space-exploration (diff)
parentMerge branch 'graphs4value:main' into datastructure (diff)
downloadrefinery-6fede3ca6dae0545083418c428e0797a01fa795c.tar.gz
refinery-6fede3ca6dae0545083418c428e0797a01fa795c.tar.zst
refinery-6fede3ca6dae0545083418c428e0797a01fa795c.zip
Merge pull request #33 from OszkarSemerath/datastructure
Initial version of State Coder
-rw-r--r--subprojects/store/build.gradle.kts5
-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/StateCoderAdapter.java23
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderBuilder.java29
-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.java22
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderAdapterImpl.java47
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderBuilderImpl.java48
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderStoreAdapterImpl.java63
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/CollectionNeighbourhoodCalculator.java131
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java235
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java132
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCodeImpl.java66
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairing.java96
-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.java152
-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/statecoding/ExperimentalSetupTest.java107
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderBuildTest.java80
23 files changed, 1427 insertions, 0 deletions
diff --git a/subprojects/store/build.gradle.kts b/subprojects/store/build.gradle.kts
index 2c485020..d653f01d 100644
--- a/subprojects/store/build.gradle.kts
+++ b/subprojects/store/build.gradle.kts
@@ -8,3 +8,8 @@ plugins {
8 id("tools.refinery.gradle.java-library") 8 id("tools.refinery.gradle.java-library")
9 id("tools.refinery.gradle.jmh") 9 id("tools.refinery.gradle.jmh")
10} 10}
11
12dependencies {
13 implementation(libs.eclipseCollections)
14 implementation(libs.eclipseCollections.api)
15}
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/StateCoderAdapter.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderAdapter.java
new file mode 100644
index 00000000..8cfd24d5
--- /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 StateCoderBuilderImpl 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..2f37584f
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderBuilder.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.statecoding;
7
8import tools.refinery.store.adapter.ModelAdapterBuilder;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.representation.AnySymbol;
11
12import java.util.Collection;
13import java.util.List;
14
15public interface StateCoderBuilder extends ModelAdapterBuilder {
16 StateCoderBuilder exclude(AnySymbol symbol);
17 default StateCoderBuilder excludeAll(Collection<? extends AnySymbol> symbols) {
18 for(var symbol : symbols) {
19 exclude(symbol);
20 }
21 return this;
22 }
23 default StateCoderBuilder excludeAll(AnySymbol... symbols) {
24 return excludeAll(List.of(symbols));
25 }
26
27 @Override
28 StateCoderStoreAdapter build(ModelStore store);
29}
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..6d8dc6c7
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java
@@ -0,0 +1,22 @@
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.model.Interpretation;
9
10import java.util.List;
11
12public interface StateEquivalenceChecker {
13 enum EquivalenceResult {
14 ISOMORPHIC, UNKNOWN, DIFFERENT
15 }
16
17 EquivalenceResult constructMorphism(
18 List<? extends Interpretation<?>> interpretations1,
19 ObjectCode code1, List<?
20 extends Interpretation<?>> interpretations2,
21 ObjectCode code2);
22}
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..b66fc86d
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderAdapterImpl.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.statecoding.internal;
7
8import tools.refinery.store.adapter.ModelStoreAdapter;
9import tools.refinery.store.model.Interpretation;
10import tools.refinery.store.model.Model;
11import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.statecoding.StateCodeCalculator;
13import tools.refinery.store.statecoding.StateCoderAdapter;
14import tools.refinery.store.statecoding.StateCoderResult;
15import tools.refinery.store.statecoding.neighbourhood.LazyNeighbourhoodCalculator;
16
17import java.util.Collection;
18import java.util.List;
19
20public class StateCoderAdapterImpl implements StateCoderAdapter {
21 final ModelStoreAdapter storeAdapter;
22 final Model model;
23 final StateCodeCalculator calculator;
24
25 StateCoderAdapterImpl(ModelStoreAdapter storeAdapter, Model model, Collection<Symbol<?>> symbols) {
26 this.storeAdapter = storeAdapter;
27 this.model = model;
28
29 List<? extends Interpretation<?>> interpretations = symbols.stream().map(model::getInterpretation).toList();
30 calculator = new LazyNeighbourhoodCalculator(interpretations);
31 }
32
33 @Override
34 public Model getModel() {
35 return model;
36 }
37
38 @Override
39 public ModelStoreAdapter getStoreAdapter() {
40 return storeAdapter;
41 }
42
43 @Override
44 public StateCoderResult calculateStateCode() {
45 return calculator.calculateCodes();
46 }
47}
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..700723f4
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderBuilderImpl.java
@@ -0,0 +1,48 @@
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.model.ModelStore;
9import tools.refinery.store.model.ModelStoreBuilder;
10import tools.refinery.store.representation.AnySymbol;
11import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.statecoding.StateCoderBuilder;
13import tools.refinery.store.statecoding.StateCoderStoreAdapter;
14
15import java.util.HashSet;
16import java.util.LinkedHashSet;
17import java.util.Set;
18
19public class StateCoderBuilderImpl implements StateCoderBuilder {
20 Set<AnySymbol> excluded = new HashSet<>();
21
22 @Override
23 public StateCoderBuilder exclude(AnySymbol symbol) {
24 excluded.add(symbol);
25 return this;
26 }
27
28 @Override
29 public boolean isConfigured() {
30 return true;
31 }
32
33 @Override
34 public void configure(ModelStoreBuilder storeBuilder) {
35 // It does not modify the build process
36 }
37
38 @Override
39 public StateCoderStoreAdapter build(ModelStore store) {
40 Set<Symbol<?>> symbols = new LinkedHashSet<>();
41 for (AnySymbol symbol : store.getSymbols()) {
42 if (!excluded.contains(symbol) && (symbol instanceof Symbol<?> typed)) {
43 symbols.add(typed);
44 }
45 }
46 return new StateCoderStoreAdapterImpl(store, symbols);
47 }
48}
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..5374755d
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderStoreAdapterImpl.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.statecoding.internal;
7
8import tools.refinery.store.map.Version;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.statecoding.StateCoderAdapter;
13import tools.refinery.store.statecoding.StateCoderStoreAdapter;
14import tools.refinery.store.statecoding.StateEquivalenceChecker;
15import tools.refinery.store.statecoding.stateequivalence.StateEquivalenceCheckerImpl;
16
17import java.util.Collection;
18import java.util.Objects;
19
20public class StateCoderStoreAdapterImpl implements StateCoderStoreAdapter {
21 final ModelStore store;
22 final Collection<Symbol<?>> symbols;
23
24 final StateEquivalenceChecker equivalenceChecker = new StateEquivalenceCheckerImpl();
25
26 StateCoderStoreAdapterImpl(ModelStore store, Collection<Symbol<?>> symbols) {
27 this.store = store;
28 this.symbols = symbols;
29 }
30
31 @Override
32 public ModelStore getStore() {
33 return store;
34 }
35
36 @Override
37 public StateEquivalenceChecker.EquivalenceResult checkEquivalence(Version v1, Version v2) {
38 if(Objects.equals(v1,v2)) {
39 return StateEquivalenceChecker.EquivalenceResult.ISOMORPHIC;
40 }
41 var model1 = this.getStore().createModelForState(v1);
42 var model2 = this.getStore().createModelForState(v2);
43
44 var s1 = model1.getAdapter(StateCoderAdapter.class).calculateStateCode();
45 var s2 = model2.getAdapter(StateCoderAdapter.class).calculateStateCode();
46
47 if(s1.modelCode() != s2.modelCode()) {
48 return StateEquivalenceChecker.EquivalenceResult.DIFFERENT;
49 }
50
51 var i1 = symbols.stream().map(model1::getInterpretation).toList();
52 var i2 = symbols.stream().map(model2::getInterpretation).toList();
53
54 return equivalenceChecker.constructMorphism(i1,s1.objectCode(),i2,s2.objectCode());
55 }
56
57 @Override
58 public StateCoderAdapter createModelAdapter(Model model) {
59 return new StateCoderAdapterImpl(this,model,symbols);
60 }
61
62
63}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/CollectionNeighbourhoodCalculator.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/CollectionNeighbourhoodCalculator.java
new file mode 100644
index 00000000..058750ee
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/CollectionNeighbourhoodCalculator.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.statecoding.neighbourhood;
7
8import org.eclipse.collections.api.set.primitive.MutableLongSet;
9import org.eclipse.collections.impl.set.mutable.primitive.LongHashSet;
10import tools.refinery.store.model.Interpretation;
11import tools.refinery.store.statecoding.StateCodeCalculator;
12import tools.refinery.store.statecoding.StateCoderResult;
13import tools.refinery.store.tuple.Tuple;
14import tools.refinery.store.tuple.Tuple0;
15
16import java.util.ArrayList;
17import java.util.LinkedHashMap;
18import java.util.List;
19import java.util.Random;
20
21public class CollectionNeighbourhoodCalculator implements StateCodeCalculator {
22 protected final List<Interpretation<?>> nullImpactValues;
23 protected final LinkedHashMap<Interpretation<?>, long[]> impactValues;
24
25 public CollectionNeighbourhoodCalculator(List<? extends Interpretation<?>> interpretations) {
26 this.nullImpactValues = new ArrayList<>();
27 this.impactValues = new LinkedHashMap<>();
28 Random random = new Random(1);
29
30 for (Interpretation<?> interpretation : interpretations) {
31 int arity = interpretation.getSymbol().arity();
32 if (arity == 0) {
33 nullImpactValues.add(interpretation);
34 } else {
35 long[] impact = new long[arity];
36 for (int i = 0; i < arity; i++) {
37 impact[i] = random.nextLong();
38 }
39 impactValues.put(interpretation, impact);
40 }
41 }
42 }
43
44 @Override
45 public StateCoderResult calculateCodes() {
46 ObjectCodeImpl previous = new ObjectCodeImpl();
47 ObjectCodeImpl next = new ObjectCodeImpl();
48
49 int previousSize = 1;
50 long lastSum;
51 boolean grows;
52
53 do{
54 for (var impactValueEntry : this.impactValues.entrySet()) {
55 Interpretation<?> interpretation = impactValueEntry.getKey();
56 long[] impact = impactValueEntry.getValue();
57 var cursor = interpretation.getAll();
58 while (cursor.move()) {
59 Tuple tuple = cursor.getKey();
60 Object value = cursor.getValue();
61 long tupleHash = getTupleHash(tuple, value, previous);
62 addHash(next, tuple, impact, tupleHash);
63 }
64 }
65
66 previous = next;
67 next = null;
68 lastSum = 0;
69 MutableLongSet codes = new LongHashSet();
70 for (int i = 0; i < previous.getSize(); i++) {
71 long objectHash = previous.get(i);
72 codes.add(objectHash);
73
74 final long shifted1 = objectHash>>> 32;
75 final long shifted2 = objectHash << 32;
76 lastSum += shifted1 + shifted2;
77 }
78 int nextSize = codes.size();
79 grows = previousSize < nextSize;
80 previousSize = nextSize;
81
82 if(grows) {
83 next = new ObjectCodeImpl(previous);
84 }
85 } while (grows);
86
87 long result = 1;
88 for (var nullImpactValue : nullImpactValues) {
89 result = result * 31 + nullImpactValue.get(Tuple0.INSTANCE).hashCode();
90 }
91 result += lastSum;
92
93 return new StateCoderResult((int) result, previous);
94 }
95
96 protected long getTupleHash(Tuple tuple, Object value, ObjectCodeImpl objectCodeImpl) {
97 long result = value.hashCode();
98 int arity = tuple.getSize();
99 if (arity == 1) {
100 result = result * 31 + objectCodeImpl.get(tuple.get(0));
101 } else if (arity == 2) {
102 result = result * 31 + objectCodeImpl.get(tuple.get(0));
103 result = result * 31 + objectCodeImpl.get(tuple.get(1));
104 if (tuple.get(0) == tuple.get(1)) {
105 result++;
106 }
107 } else if (arity > 2) {
108 for (int i = 0; i < arity; i++) {
109 result = result * 31 + objectCodeImpl.get(tuple.get(i));
110 }
111 }
112 return result;
113 }
114
115 protected void addHash(ObjectCodeImpl objectCodeImpl, Tuple tuple, long[] impact, long tupleHashCode) {
116 if (tuple.getSize() == 1) {
117 addHash(objectCodeImpl, tuple.get(0), impact[0], tupleHashCode);
118 } else if (tuple.getSize() == 2) {
119 addHash(objectCodeImpl, tuple.get(0), impact[0], tupleHashCode);
120 addHash(objectCodeImpl, tuple.get(1), impact[1], tupleHashCode);
121 } else if (tuple.getSize() > 2) {
122 for (int i = 0; i < tuple.getSize(); i++) {
123 addHash(objectCodeImpl, tuple.get(i), impact[i], tupleHashCode);
124 }
125 }
126 }
127
128 protected void addHash(ObjectCodeImpl objectCodeImpl, int o, long impact, long tupleHash) {
129 objectCodeImpl.set(o, objectCodeImpl.get(o) + tupleHash * impact);
130 }
131}
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..79317679
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java
@@ -0,0 +1,235 @@
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.map.primitive.LongIntMap;
9import org.eclipse.collections.impl.map.mutable.primitive.LongIntHashMap;
10import tools.refinery.store.map.Cursor;
11import tools.refinery.store.model.Interpretation;
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.ArrayList;
18import java.util.LinkedHashMap;
19import java.util.List;
20import java.util.Random;
21
22public class LazyNeighbourhoodCalculator implements StateCodeCalculator {
23 protected final List<Interpretation<?>> nullImpactValues;
24 protected final LinkedHashMap<Interpretation<?>, long[]> impactValues;
25
26 public LazyNeighbourhoodCalculator(List<? extends Interpretation<?>> interpretations) {
27 this.nullImpactValues = new ArrayList<>();
28 this.impactValues = new LinkedHashMap<>();
29 Random random = new Random(1);
30
31 for (Interpretation<?> interpretation : interpretations) {
32 int arity = interpretation.getSymbol().arity();
33 if (arity == 0) {
34 nullImpactValues.add(interpretation);
35 } else {
36 long[] impact = new long[arity];
37 for (int i = 0; i < arity; i++) {
38 impact[i] = random.nextInt();
39 }
40 impactValues.put(interpretation, impact);
41 }
42 }
43 }
44
45 public StateCoderResult calculateCodes() {
46 ObjectCodeImpl previous = new ObjectCodeImpl();
47 ObjectCodeImpl next = new ObjectCodeImpl();
48 LongIntMap hash2Amount = new LongIntHashMap();
49
50 long lastSum;
51 int lastSize = 1;
52 boolean grows;
53
54 do {
55 constructNextObjectCodes(previous, next, hash2Amount);
56
57 LongIntHashMap nextHash2Amount = new LongIntHashMap();
58 lastSum = calculateLastSum(previous, next, hash2Amount, nextHash2Amount);
59
60 previous = next;
61 next = null;
62
63 int nextSize = nextHash2Amount.size();
64 grows = nextSize > lastSize;
65 lastSize = nextSize;
66
67 if (grows) {
68 next = new ObjectCodeImpl(previous);
69 }
70 } while (grows);
71
72 long result = 1;
73 for (var nullImpactValue : nullImpactValues) {
74 result = result * 31 + nullImpactValue.get(Tuple0.INSTANCE).hashCode();
75 }
76 result += lastSum;
77
78 return new StateCoderResult((int) result, previous);
79 }
80
81 private long calculateLastSum(ObjectCodeImpl previous, ObjectCodeImpl next, LongIntMap hash2Amount,
82 LongIntHashMap nextHash2Amount) {
83 long lastSum = 0;
84 for (int i = 0; i < next.getSize(); i++) {
85 final long hash;
86 if (isUnique(hash2Amount, previous, i)) {
87 hash = previous.get(i);
88 next.set(i, hash);
89 } else {
90 hash = next.get(i);
91 }
92
93 final int amount = nextHash2Amount.get(hash);
94 nextHash2Amount.put(hash, amount + 1);
95
96 final long shifted1 = hash >>> 8;
97 final long shifted2 = hash << 8;
98 final long shifted3 = hash >> 2;
99 lastSum += shifted1*shifted3 + shifted2;
100 }
101 return lastSum;
102 }
103
104 private void constructNextObjectCodes(ObjectCodeImpl previous, ObjectCodeImpl next, LongIntMap hash2Amount) {
105 for (var impactValueEntry : this.impactValues.entrySet()) {
106 Interpretation<?> interpretation = impactValueEntry.getKey();
107 var cursor = interpretation.getAll();
108 int arity = interpretation.getSymbol().arity();
109 long[] impactValue = impactValueEntry.getValue();
110
111 if (arity == 1) {
112 while (cursor.move()) {
113 lazyImpactCalculation1(hash2Amount, previous, next, impactValue, cursor);
114 }
115 } else if (arity == 2) {
116 while (cursor.move()) {
117 lazyImpactCalculation2(hash2Amount, previous, next, impactValue, cursor);
118 }
119 } else {
120 while (cursor.move()) {
121 lazyImpactCalculationN(hash2Amount, previous, next, impactValue, cursor);
122 }
123 }
124 }
125 }
126
127 private boolean isUnique(LongIntMap hash2Amount, ObjectCodeImpl objectCodeImpl, int object) {
128 final long hash = objectCodeImpl.get(object);
129 if(hash == 0) {
130 return false;
131 }
132 final int amount = hash2Amount.get(hash);
133 return amount == 1;
134 }
135
136 private void lazyImpactCalculation1(LongIntMap hash2Amount, ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues, Cursor<Tuple, ?> cursor) {
137
138 Tuple tuple = cursor.getKey();
139 int o = tuple.get(0);
140
141 if (isUnique(hash2Amount, previous, o)) {
142 next.ensureSize(o);
143 } else {
144 Object value = cursor.getValue();
145 long tupleHash = getTupleHash1(tuple, value, previous);
146
147 addHash(next, o, impactValues[0], tupleHash);
148 }
149 }
150
151 private void lazyImpactCalculation2(LongIntMap hash2Amount, ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues, Cursor<Tuple, ?> cursor) {
152 Tuple tuple = cursor.getKey();
153 int o1 = tuple.get(0);
154 int o2 = tuple.get(1);
155
156 boolean u1 = isUnique(hash2Amount, previous, o1);
157 boolean u2 = isUnique(hash2Amount, previous, o2);
158
159 if (u1 && u2) {
160 next.ensureSize(o1);
161 next.ensureSize(o2);
162 } else {
163 Object value = cursor.getValue();
164 long tupleHash = getTupleHash2(tuple, value, previous);
165
166 if (!u1) {
167 addHash(next, o1, impactValues[0], tupleHash);
168 next.ensureSize(o2);
169 }
170 if (!u2) {
171 next.ensureSize(o1);
172 addHash(next, o2, impactValues[1], tupleHash);
173 }
174 }
175 }
176
177 private void lazyImpactCalculationN(LongIntMap hash2Amount, ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues, Cursor<Tuple, ?> cursor) {
178 Tuple tuple = cursor.getKey();
179
180 boolean[] uniques = new boolean[tuple.getSize()];
181 boolean allUnique = true;
182 for (int i = 0; i < tuple.getSize(); i++) {
183 final boolean isUnique = isUnique(hash2Amount, previous, tuple.get(i));
184 uniques[i] = isUnique;
185 allUnique &= isUnique;
186 }
187
188 if (allUnique) {
189 for (int i = 0; i < tuple.getSize(); i++) {
190 next.ensureSize(tuple.get(i));
191 }
192 } else {
193 Object value = cursor.getValue();
194 long tupleHash = getTupleHashN(tuple, value, previous);
195
196 for (int i = 0; i < tuple.getSize(); i++) {
197 int o = tuple.get(i);
198 if (!uniques[i]) {
199 addHash(next, o, impactValues[i], tupleHash);
200 } else {
201 next.ensureSize(o);
202 }
203 }
204 }
205 }
206
207 private long getTupleHash1(Tuple tuple, Object value, ObjectCodeImpl objectCodeImpl) {
208 long result = value.hashCode();
209 result = result * 31 + objectCodeImpl.get(tuple.get(0));
210 return result;
211 }
212
213 private long getTupleHash2(Tuple tuple, Object value, ObjectCodeImpl objectCodeImpl) {
214 long result = value.hashCode();
215 result = result * 31 + objectCodeImpl.get(tuple.get(0));
216 result = result * 31 + objectCodeImpl.get(tuple.get(1));
217 if (tuple.get(0) == tuple.get(1)) {
218 result*=31;
219 }
220 return result;
221 }
222
223 private long getTupleHashN(Tuple tuple, Object value, ObjectCodeImpl objectCodeImpl) {
224 long result = value.hashCode();
225 for (int i = 0; i < tuple.getSize(); i++) {
226 result = result * 31 + objectCodeImpl.get(tuple.get(i));
227 }
228 return result;
229 }
230
231 protected void addHash(ObjectCodeImpl objectCodeImpl, int o, long impact, long tupleHash) {
232 long x = tupleHash * impact;
233 objectCodeImpl.set(o, objectCodeImpl.get(o) + x);
234 }
235}
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..212291c3
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java
@@ -0,0 +1,132 @@
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.MutableLongSet;
9import org.eclipse.collections.impl.set.mutable.primitive.LongHashSet;
10import tools.refinery.store.model.Interpretation;
11import tools.refinery.store.statecoding.StateCodeCalculator;
12import tools.refinery.store.statecoding.StateCoderResult;
13import tools.refinery.store.tuple.Tuple;
14import tools.refinery.store.tuple.Tuple0;
15
16import java.util.ArrayList;
17import java.util.LinkedHashMap;
18import java.util.List;
19import java.util.Random;
20
21public class NeighbourhoodCalculator implements StateCodeCalculator {
22 protected final List<Interpretation<?>> nullImpactValues;
23 protected final LinkedHashMap<Interpretation<?>, long[]> impactValues;
24
25 public NeighbourhoodCalculator(List<? extends Interpretation<?>> interpretations) {
26 this.nullImpactValues = new ArrayList<>();
27 this.impactValues = new LinkedHashMap<>();
28 Random random = new Random(1);
29
30 for (Interpretation<?> interpretation : interpretations) {
31 int arity = interpretation.getSymbol().arity();
32 if (arity == 0) {
33 nullImpactValues.add(interpretation);
34 } else {
35 long[] impact = new long[arity];
36 for (int i = 0; i < arity; i++) {
37 impact[i] = random.nextLong();
38 }
39 impactValues.put(interpretation, impact);
40 }
41 }
42 }
43
44 @Override
45 public StateCoderResult calculateCodes() {
46 ObjectCodeImpl previous = new ObjectCodeImpl();
47 ObjectCodeImpl next = new ObjectCodeImpl();
48
49 int previousSize = 1;
50 long lastSum;
51 boolean grows;
52
53 do{
54 for (var impactValueEntry : this.impactValues.entrySet()) {
55 Interpretation<?> interpretation = impactValueEntry.getKey();
56 long[] impact = impactValueEntry.getValue();
57 var cursor = interpretation.getAll();
58 while (cursor.move()) {
59 Tuple tuple = cursor.getKey();
60 Object value = cursor.getValue();
61 long tupleHash = getTupleHash(tuple, value, previous);
62 addHash(next, tuple, impact, tupleHash);
63 }
64 }
65
66 previous = next;
67 next = null;
68 lastSum = 0;
69 MutableLongSet codes = new LongHashSet();
70 for (int i = 0; i < previous.getSize(); i++) {
71 long objectHash = previous.get(i);
72 codes.add(objectHash);
73
74 final long shifted1 = objectHash>>> 32;
75 final long shifted2 = objectHash << 32;
76 lastSum += shifted1 + shifted2;
77 }
78 int nextSize = codes.size();
79 grows = previousSize < nextSize;
80 previousSize = nextSize;
81
82 if(grows) {
83 next = new ObjectCodeImpl(previous);
84 }
85 } while (grows);
86
87 long result = 1;
88 for (var nullImpactValue : nullImpactValues) {
89 result = result * 31 + nullImpactValue.get(Tuple0.INSTANCE).hashCode();
90 }
91 result += lastSum;
92
93 return new StateCoderResult((int) result, previous);
94 }
95
96 protected long getTupleHash(Tuple tuple, Object value, ObjectCodeImpl objectCodeImpl) {
97 long result = value.hashCode();
98 int arity = tuple.getSize();
99 if (arity == 1) {
100 result = result * 31 + objectCodeImpl.get(tuple.get(0));
101 } else if (arity == 2) {
102 result = result * 31 + objectCodeImpl.get(tuple.get(0));
103 result = result * 31 + objectCodeImpl.get(tuple.get(1));
104 if (tuple.get(0) == tuple.get(1)) {
105 result++;
106 }
107 } else if (arity > 2) {
108 for (int i = 0; i < arity; i++) {
109 result = result * 31 + objectCodeImpl.get(tuple.get(i));
110 }
111 }
112 return result;
113 }
114
115 protected void addHash(ObjectCodeImpl objectCodeImpl, Tuple tuple, long[] impact, long tupleHashCode) {
116 if (tuple.getSize() == 1) {
117 addHash(objectCodeImpl, tuple.get(0), impact[0], tupleHashCode);
118 } else if (tuple.getSize() == 2) {
119 addHash(objectCodeImpl, tuple.get(0), impact[0], tupleHashCode);
120 addHash(objectCodeImpl, tuple.get(1), impact[1], tupleHashCode);
121 } else if (tuple.getSize() > 2) {
122 for (int i = 0; i < tuple.getSize(); i++) {
123 addHash(objectCodeImpl, tuple.get(i), impact[i], tupleHashCode);
124 }
125 }
126 }
127
128 protected void addHash(ObjectCodeImpl objectCodeImpl, int o, long impact, long tupleHash) {
129 objectCodeImpl.set(o, objectCodeImpl.get(o) + tupleHash * impact);
130 }
131
132}
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..08e3a90b
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCodeImpl.java
@@ -0,0 +1,66 @@
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
16 public ObjectCodeImpl() {
17 vector = new long[10];
18 size = 0;
19 }
20
21 public ObjectCodeImpl(ObjectCodeImpl sameSize) {
22 this.vector = new long[sameSize.size];
23 this.size = sameSize.size;
24 }
25
26 public void ensureSize(int object) {
27 if(object >= size) {
28 size = object+1;
29 }
30
31 if(object >= vector.length) {
32 int newLength = vector.length*2;
33 while(object >= newLength) {
34 newLength*=2;
35 }
36
37 long[] newVector = new long[newLength];
38 System.arraycopy(vector, 0, newVector, 0, vector.length);
39 this.vector = newVector;
40 }
41 }
42
43 public long get(int object) {
44 if(object < vector.length) {
45 return vector[object];
46 } else {
47 return 0;
48 }
49 }
50
51 public void set(int object, long value) {
52 ensureSize(object);
53 vector[object]=value;
54 }
55
56 public int getSize() {
57 return this.size;
58 }
59
60 @Override
61 public String toString() {
62 return "ObjectCodeImpl{" +
63 "vector=" + Arrays.toString(Arrays.copyOf(vector,this.size)) +
64 '}';
65 }
66}
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..2877bd0f
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairing.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.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;
11import org.eclipse.collections.impl.list.Interval;
12import org.eclipse.collections.impl.set.mutable.primitive.IntHashSet;
13
14import java.util.ArrayList;
15import java.util.Arrays;
16import java.util.List;
17
18public class CombinationNodePairing implements NodePairing {
19 private final int[] left;
20 private final int[] right;
21
22 CombinationNodePairing(IntSet left, IntHashSet right) {
23 this.left = left.toArray();
24 this.right = right.toArray();
25
26 Arrays.sort(this.left);
27 Arrays.sort(this.right);
28 }
29
30 @Override
31 public int size() {
32 return left.length;
33 }
34
35 static final int LIMIT = 5;
36 static final List<List<int[]>> permutations = new ArrayList<>();
37
38 /**
39 * Generates and stores permutations up to a given size. If the number would be more than a limit, it provides a
40 * single permutation only.
41 *
42 * @param max The max number in the permutation
43 * @return A complete list of permutations of numbers 0...max, or a single permutation.
44 */
45 public static List<int[]> getPermutations(int max) {
46 if (max < permutations.size()) {
47 return permutations.get(max);
48 }
49 if (max == 0) {
50 List<int[]> result = new ArrayList<>();
51 result.add(new int[1]);
52 permutations.add(result);
53 return result;
54 }
55 List<int[]> result = new ArrayList<>();
56 List<int[]> previousPermutations = getPermutations(max - 1);
57 for (var permutation : previousPermutations) {
58 for (int pos = 0; pos <= max; pos++) {
59 int[] newPermutation = new int[max + 1];
60 if (pos >= 0)
61 System.arraycopy(permutation, 0, newPermutation, 0, pos);
62 newPermutation[pos] = max;
63 if (max - (pos + 1) >= 0)
64 System.arraycopy(permutation, pos + 1, newPermutation, pos + 1 + 1, max - (pos + 1));
65 result.add(newPermutation);
66 }
67 }
68 permutations.add(result);
69 return result;
70 }
71
72 @Override
73 public List<IntIntMap> permutations() {
74 final var interval = Interval.zeroTo(this.size() - 1);
75
76 if (isComplete()) {
77 final List<int[]> p = getPermutations(this.size() - 1);
78 return p.stream().map(x -> constructPermutationMap(interval, x)).toList();
79 } else {
80 return List.of(constructTrivialMap(interval));
81 }
82 }
83
84 private IntIntMap constructTrivialMap(Interval interval) {
85 return IntIntMaps.immutable.from(interval, l -> left[l], r -> right[r]);
86 }
87
88 private IntIntMap constructPermutationMap(Interval interval, int[] permutation) {
89 return IntIntMaps.immutable.from(interval, l -> left[l], r -> right[permutation[r]]);
90 }
91
92 @Override
93 public boolean isComplete() {
94 return this.size() <= LIMIT;
95 }
96}
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..7e5db7a3
--- /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.impl.set.mutable.primitive.IntHashSet;
10
11import java.util.List;
12
13public interface NodePairing {
14
15 int size();
16 List<IntIntMap> permutations();
17
18 boolean isComplete();
19
20 static NodePairing constructNodePairing(IntHashSet left, IntHashSet 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..6be0f3e7
--- /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<List<IntIntMap>> permutationsGroups;
16 private final int[] selection;
17 private boolean hasNext;
18
19 PermutationMorphism(IntIntMap object2PermutationGroup,
20 List<List<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..fd704086
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java
@@ -0,0 +1,152 @@
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.impl.map.mutable.primitive.IntIntHashMap;
10import org.eclipse.collections.impl.map.mutable.primitive.LongObjectHashMap;
11import org.eclipse.collections.impl.set.mutable.primitive.IntHashSet;
12import tools.refinery.store.model.Interpretation;
13import tools.refinery.store.statecoding.Morphism;
14import tools.refinery.store.statecoding.ObjectCode;
15import tools.refinery.store.statecoding.StateEquivalenceChecker;
16import tools.refinery.store.tuple.Tuple;
17
18import java.util.ArrayList;
19import java.util.List;
20import java.util.Objects;
21
22public class StateEquivalenceCheckerImpl implements StateEquivalenceChecker {
23 public static final int LIMIT = 1000;
24
25 @Override
26 public EquivalenceResult constructMorphism(List<? extends Interpretation<?>> interpretations1,
27 ObjectCode code1,
28 List<? extends Interpretation<?>> interpretations2,
29 ObjectCode code2) {
30 if (code1.getSize() != code2.getSize()) {
31 return EquivalenceResult.DIFFERENT;
32 }
33
34 IntIntHashMap object2PermutationGroup = new IntIntHashMap();
35 List<List<IntIntMap>> permutationsGroups = new ArrayList<>();
36
37 final EquivalenceResult permutations = constructPermutationNavigation(indexByHash(code1), indexByHash(code2),
38 object2PermutationGroup, permutationsGroups);
39
40 if (permutations == EquivalenceResult.DIFFERENT) {
41 return EquivalenceResult.DIFFERENT;
42 }
43
44 boolean hasNext;
45 PermutationMorphism morphism = new PermutationMorphism(object2PermutationGroup, permutationsGroups);
46 int tried = 0;
47 do {
48 if (testMorphism(interpretations1, interpretations2, morphism)) {
49 return permutations;
50 }
51
52 if(tried >= LIMIT) {
53 return EquivalenceResult.UNKNOWN;
54 }
55
56 hasNext = morphism.next();
57 tried++;
58 } while (hasNext);
59
60 return EquivalenceResult.DIFFERENT;
61 }
62
63 private LongObjectHashMap<IntHashSet> indexByHash(ObjectCode code) {
64 LongObjectHashMap<IntHashSet> result = new LongObjectHashMap<>();
65 for (int o = 0; o < code.getSize(); o++) {
66 long hash = code.get(o);
67 var equivalenceClass = result.get(hash);
68 if (equivalenceClass == null) {
69 equivalenceClass = new IntHashSet();
70 result.put(hash, equivalenceClass);
71 }
72 equivalenceClass.add(o);
73 }
74 return result;
75 }
76
77 private EquivalenceResult constructPermutationNavigation(LongObjectHashMap<IntHashSet> map1,
78 LongObjectHashMap<IntHashSet> map2,
79 IntIntHashMap emptyMapToListOfOptions,
80 List<List<IntIntMap>> emptyListOfOptions) {
81 if (map1.size() != map2.size()) {
82 return EquivalenceResult.DIFFERENT;
83 }
84
85 var iterator = map1.keySet().longIterator();
86
87 boolean allComplete = true;
88
89 while (iterator.hasNext()) {
90 long hash = iterator.next();
91 var set1 = map1.get(hash);
92 var set2 = map2.get(hash);
93 if (set2 == null) {
94 return EquivalenceResult.DIFFERENT;
95 }
96
97 var pairing = NodePairing.constructNodePairing(set1, set2);
98 if (pairing == null) {
99 return EquivalenceResult.DIFFERENT;
100 }
101
102 allComplete &= pairing.isComplete();
103
104 final int optionIndex = emptyListOfOptions.size();
105 set1.forEach(key -> emptyMapToListOfOptions.put(key, optionIndex));
106 emptyListOfOptions.add(pairing.permutations());
107 }
108 if(allComplete) {
109 return EquivalenceResult.ISOMORPHIC;
110 } else {
111 return EquivalenceResult.UNKNOWN;
112 }
113 }
114
115 private boolean testMorphism(List<? extends Interpretation<?>> s, List<? extends Interpretation<?>> t, Morphism m) {
116 for (int interpretationIndex = 0; interpretationIndex < s.size(); interpretationIndex++) {
117 var sI = s.get(interpretationIndex);
118 var tI = t.get(interpretationIndex);
119
120 var cursor = sI.getAll();
121 while (cursor.move()) {
122 final Tuple sTuple = cursor.getKey();
123 final Object sValue = cursor.getValue();
124
125 final Tuple tTuple = apply(sTuple, m);
126 final Object tValue = tI.get(tTuple);
127
128 if (!Objects.equals(sValue, tValue)) {
129 return false;
130 }
131 }
132 }
133 return true;
134 }
135
136 private Tuple apply(Tuple t, Morphism m) {
137 final int arity = t.getSize();
138 if (arity == 0) {
139 return Tuple.of();
140 } else if (arity == 1) {
141 return Tuple.of(m.get(t.get(0)));
142 } else if (arity == 2) {
143 return Tuple.of(m.get(t.get(0)), m.get(t.get(1)));
144 } else {
145 int[] newTupleIndices = new int[arity];
146 for (int i = 0; i < arity; i++) {
147 newTupleIndices[i] = m.get(t.get(i));
148 }
149 return Tuple.of(newTupleIndices);
150 }
151 }
152}
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/statecoding/ExperimentalSetupTest.java b/subprojects/store/src/test/java/tools/refinery/store/statecoding/ExperimentalSetupTest.java
new file mode 100644
index 00000000..87b1623c
--- /dev/null
+++ b/subprojects/store/src/test/java/tools/refinery/store/statecoding/ExperimentalSetupTest.java
@@ -0,0 +1,107 @@
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.ModelStore;
11import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.tuple.Tuple;
13
14import java.util.*;
15
16class ExperimentalSetupTest {
17 public static void generate(int size) {
18 Symbol<Boolean> person = new Symbol<>("Person", 1, Boolean.class, false);
19 Symbol<Boolean> friend = new Symbol<>("friend", 2, Boolean.class, false);
20
21 var store = ModelStore.builder()
22 .symbols(person, friend)
23 .with(StateCoderAdapter
24 .builder())
25 .build();
26
27 Set<Version> versions = new HashSet<>();
28 Map<Integer, List<Version>> codes = new HashMap<>();
29
30 var empty = store.createEmptyModel();
31 var pI = empty.getInterpretation(person);
32
33 for (int i = 0; i < size; i++) {
34 pI.put(Tuple.of(i), true);
35 }
36
37 var emptyVersion = empty.commit();
38 versions.add(emptyVersion);
39 var emptyCode = empty.getAdapter(StateCoderAdapter.class).calculateModelCode();
40 List<Version> emptyList = new ArrayList<>();
41 emptyList.add(emptyVersion);
42 codes.put(emptyCode, emptyList);
43
44 var storeAdapter = store.getAdapter(StateCoderStoreAdapter.class);
45
46 int dif = 0;
47 int iso = 0;
48 int unk = 0;
49
50 //int step = 0
51
52 for (int i = 0; i < size; i++) {
53 for (int j = 0; j < size; j++) {
54 var previousVersions = new HashSet<>(versions);
55 for (var version : previousVersions) {
56
57 var model = store.createModelForState(version);
58 model.getInterpretation(friend).put(Tuple.of(i, j), true);
59
60 Version version1 = model.commit();
61 var stateCode = model.getAdapter(StateCoderAdapter.class).calculateStateCode();
62 int code = stateCode.modelCode();
63 //System.out.println(step+++" ->" +code);
64 if (codes.containsKey(code)) {
65 Version similar = codes.get(code).get(0);
66
67 var outcome = storeAdapter.checkEquivalence(version1, similar);
68 if (outcome == StateEquivalenceChecker.EquivalenceResult.DIFFERENT) {
69 System.out.println();
70 var c = model.getInterpretation(friend).getAll();
71 while (c.move()) {
72 System.out.println(c.getKey().toString());
73 }
74 System.out.println("vs");
75 var c2 = store.createModelForState(similar).getInterpretation(friend).getAll();
76 while (c2.move()) {
77 System.out.println(c2.getKey().toString());
78 }
79
80 dif++;
81 } else if (outcome == StateEquivalenceChecker.EquivalenceResult.UNKNOWN) {
82 unk++;
83 } else {
84 iso++;
85 }
86 } else {
87 versions.add(version1);
88
89 List<Version> newList = new ArrayList<>();
90 newList.add(version1);
91 codes.put(code, newList);
92 }
93 }
94 }
95 }
96
97 System.out.printf("v=%d i=%d d=%d u=%d\n", versions.size(), iso, dif, unk);
98 }
99
100 @Test
101 void runTests() {
102 for (int i = 0; i < 5; i++) {
103 System.out.println("size = " + i);
104 generate(i);
105 }
106 }
107}
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..b0b80af7
--- /dev/null
+++ b/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderBuildTest.java
@@ -0,0 +1,80 @@
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 simpleStateCoderTest() {
23 var store = ModelStore.builder()
24 .symbols(person, age, friend)
25 .with(StateCoderAdapter
26 .builder())
27 .build();
28
29 var model = store.createEmptyModel();
30 var stateCoder = model.getAdapter(StateCoderAdapter.class);
31 assertNotNull(stateCoder);
32
33 var personI = model.getInterpretation(person);
34 var friendI = model.getInterpretation(friend);
35 var ageI = model.getInterpretation(age);
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 private static void fill(Interpretation<Boolean> personI, Interpretation<Boolean> friendI, Interpretation<Integer> ageI) {
72 personI.put(Tuple.of(1), true);
73 personI.put(Tuple.of(2), true);
74
75 ageI.put(Tuple.of(1), 5);
76 ageI.put(Tuple.of(2), 4);
77
78 friendI.put(Tuple.of(1, 2), true);
79 }
80}