aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <marussy@mit.bme.hu>2023-12-25 13:57:04 +0100
committerLibravatar GitHub <noreply@github.com>2023-12-25 13:57:04 +0100
commitfa25d614455dd0872bfa6ff88c5be3ecfc33a6af (patch)
tree84aba7f8ce90ff5aea38c912b3999ec7810f6f44 /subprojects
parentchore: upgrade to Eclipse 2023-12 (diff)
parentrefactor(generator): scope overrides (diff)
downloadrefinery-fa25d614455dd0872bfa6ff88c5be3ecfc33a6af.tar.gz
refinery-fa25d614455dd0872bfa6ff88c5be3ecfc33a6af.tar.zst
refinery-fa25d614455dd0872bfa6ff88c5be3ecfc33a6af.zip
Merge pull request #50 from kris7t/generator-roundtrip
Round-trip model serialization and command line application
Diffstat (limited to 'subprojects')
-rw-r--r--subprojects/frontend/config/graphvizUMDVitePlugin.ts12
-rw-r--r--subprojects/frontend/package.json54
-rw-r--r--subprojects/generator-cli/build.gradle.kts24
-rw-r--r--subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/RefineryCli.java64
-rw-r--r--subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/commands/GenerateCommand.java87
-rw-r--r--subprojects/generator/build.gradle.kts1
-rw-r--r--subprojects/generator/src/main/java/tools/refinery/generator/ModelGenerator.java30
-rw-r--r--subprojects/generator/src/main/java/tools/refinery/generator/ModelGeneratorFactory.java7
-rw-r--r--subprojects/generator/src/main/java/tools/refinery/generator/ModelSemantics.java2
-rw-r--r--subprojects/generator/src/main/java/tools/refinery/generator/ProblemLoader.java88
-rw-r--r--subprojects/generator/src/test/java/tools/refinery/generator/ProblemLoaderTest.java129
-rw-r--r--subprojects/language-model/src/main/resources/model/problem.ecore2
-rw-r--r--subprojects/language-semantics/build.gradle.kts1
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ModelInitializer.java64
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ProblemTraceImpl.java23
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SemanticsUtils.java41
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java335
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/DecisionTree.java4
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/DecisionTreeNode.java2
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/IntermediateNode.java5
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/NullaryMutableSeed.java4
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/TerminalNode.java7
-rw-r--r--subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/SolutionSerializerTest.java164
-rw-r--r--subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/internal/DecisionTreeTests.java10
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java8
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java18
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/serializer/PreferShortAssertionsProblemSemanticSequencer.java6
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/serializer/ProblemCrossReferenceSerializer.java167
-rw-r--r--subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java87
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstExplorer.java4
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstStoreManager.java2
-rw-r--r--subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/PredicateScopeTest.java15
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/MapBasedSeed.java8
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java2
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInfo.java4
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInitializer.java52
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceTranslator.java104
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInfo.java3
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInitializer.java84
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceTranslator.java95
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java54
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfo.java6
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfoBuilder.java84
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java34
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTest.java30
45 files changed, 1774 insertions, 253 deletions
diff --git a/subprojects/frontend/config/graphvizUMDVitePlugin.ts b/subprojects/frontend/config/graphvizUMDVitePlugin.ts
index ab726e4f..0c3c9aa0 100644
--- a/subprojects/frontend/config/graphvizUMDVitePlugin.ts
+++ b/subprojects/frontend/config/graphvizUMDVitePlugin.ts
@@ -27,13 +27,15 @@ export default function graphvizUMDVitePlugin(): PluginOption {
27 async buildStart() { 27 async buildStart() {
28 const issuer = 28 const issuer =
29 root === undefined ? issuerFileName : path.join(root, issuerFileName); 29 root === undefined ? issuerFileName : path.join(root, issuerFileName);
30 const resolvedPath = pnpapi.resolveRequest( 30 // Since https://github.com/hpcc-systems/hpcc-js-wasm/commit/15e1ace5edae7f94714e547a3ac20e0e17cd6b0c,
31 '@hpcc-js/wasm/graphviz', 31 // hpcc-js has both a `.cjs` and a `.umd.js` build. PnPAPI will find the former, but we need the latter.
32 issuer, 32 const resolvedPath = pnpapi
33 ); 33 .resolveRequest('@hpcc-js/wasm/graphviz', issuer)
34 if (resolvedPath === null) { 34 ?.replace(/\.cjs$/, '.umd.js');
35 if (resolvedPath === undefined) {
35 return; 36 return;
36 } 37 }
38 console.log(resolvedPath);
37 if (command === 'serve') { 39 if (command === 'serve') {
38 url = `/@fs/${resolvedPath}`; 40 url = `/@fs/${resolvedPath}`;
39 } else { 41 } else {
diff --git a/subprojects/frontend/package.json b/subprojects/frontend/package.json
index f55093ce..9dec8b32 100644
--- a/subprojects/frontend/package.json
+++ b/subprojects/frontend/package.json
@@ -33,23 +33,23 @@
33 "@codemirror/language": "^6.9.3", 33 "@codemirror/language": "^6.9.3",
34 "@codemirror/lint": "^6.4.2", 34 "@codemirror/lint": "^6.4.2",
35 "@codemirror/search": "^6.5.5", 35 "@codemirror/search": "^6.5.5",
36 "@codemirror/state": "^6.3.2", 36 "@codemirror/state": "^6.3.3",
37 "@codemirror/view": "^6.22.1", 37 "@codemirror/view": "^6.22.3",
38 "@emotion/react": "^11.11.1", 38 "@emotion/react": "^11.11.3",
39 "@emotion/styled": "^11.11.0", 39 "@emotion/styled": "^11.11.0",
40 "@fontsource-variable/jetbrains-mono": "^5.0.18", 40 "@fontsource-variable/jetbrains-mono": "^5.0.19",
41 "@fontsource-variable/open-sans": "^5.0.18", 41 "@fontsource-variable/open-sans": "^5.0.21",
42 "@hpcc-js/wasm": "^2.15.0", 42 "@hpcc-js/wasm": "^2.15.3",
43 "@lezer/common": "^1.1.1", 43 "@lezer/common": "^1.1.2",
44 "@lezer/highlight": "^1.2.0", 44 "@lezer/highlight": "^1.2.0",
45 "@lezer/lr": "^1.3.14", 45 "@lezer/lr": "^1.3.14",
46 "@material-icons/svg": "^1.0.33", 46 "@material-icons/svg": "^1.0.33",
47 "@mui/icons-material": "5.14.19", 47 "@mui/icons-material": "^5.15.1",
48 "@mui/material": "5.14.19", 48 "@mui/material": "^5.15.1",
49 "@mui/system": "^5.14.19", 49 "@mui/system": "^5.15.1",
50 "@mui/x-data-grid": "^6.18.2", 50 "@mui/x-data-grid": "^6.18.6",
51 "ansi-styles": "^6.2.1", 51 "ansi-styles": "^6.2.1",
52 "csstype": "^3.1.2", 52 "csstype": "^3.1.3",
53 "d3": "^7.8.5", 53 "d3": "^7.8.5",
54 "d3-graphviz": "patch:d3-graphviz@npm%3A5.2.0#~/.yarn/patches/d3-graphviz-npm-5.2.0-161b1fbad4.patch", 54 "d3-graphviz": "patch:d3-graphviz@npm%3A5.2.0#~/.yarn/patches/d3-graphviz-npm-5.2.0-161b1fbad4.patch",
55 "d3-selection": "^3.0.0", 55 "d3-selection": "^3.0.0",
@@ -65,7 +65,7 @@
65 "notistack": "^3.0.1", 65 "notistack": "^3.0.1",
66 "react": "^18.2.0", 66 "react": "^18.2.0",
67 "react-dom": "^18.2.0", 67 "react-dom": "^18.2.0",
68 "react-resize-detector": "^9.1.0", 68 "react-resize-detector": "^9.1.1",
69 "xstate": "^4.38.3", 69 "xstate": "^4.38.3",
70 "zod": "^3.22.4" 70 "zod": "^3.22.4"
71 }, 71 },
@@ -75,38 +75,38 @@
75 "@types/d3-graphviz": "^2.6.10", 75 "@types/d3-graphviz": "^2.6.10",
76 "@types/d3-selection": "^3.0.10", 76 "@types/d3-selection": "^3.0.10",
77 "@types/d3-zoom": "^3.0.8", 77 "@types/d3-zoom": "^3.0.8",
78 "@types/eslint": "^8.44.8", 78 "@types/eslint": "^8.56.0",
79 "@types/html-minifier-terser": "^7.0.2", 79 "@types/html-minifier-terser": "^7.0.2",
80 "@types/lodash-es": "^4.17.12", 80 "@types/lodash-es": "^4.17.12",
81 "@types/micromatch": "^4.0.6", 81 "@types/micromatch": "^4.0.6",
82 "@types/ms": "^0.7.34", 82 "@types/ms": "^0.7.34",
83 "@types/node": "^20.10.2", 83 "@types/node": "^20.10.5",
84 "@types/pnpapi": "^0.0.5", 84 "@types/pnpapi": "^0.0.5",
85 "@types/react": "^18.2.41", 85 "@types/react": "^18.2.45",
86 "@types/react-dom": "^18.2.17", 86 "@types/react-dom": "^18.2.18",
87 "@typescript-eslint/eslint-plugin": "^6.13.1", 87 "@typescript-eslint/eslint-plugin": "^6.15.0",
88 "@typescript-eslint/parser": "^6.13.1", 88 "@typescript-eslint/parser": "^6.15.0",
89 "@vitejs/plugin-react-swc": "^3.5.0", 89 "@vitejs/plugin-react-swc": "^3.5.0",
90 "@xstate/cli": "^0.5.11", 90 "@xstate/cli": "^0.5.15",
91 "cross-env": "^7.0.3", 91 "cross-env": "^7.0.3",
92 "eslint": "^8.55.0", 92 "eslint": "^8.56.0",
93 "eslint-config-airbnb": "^19.0.4", 93 "eslint-config-airbnb": "^19.0.4",
94 "eslint-config-airbnb-typescript": "^17.1.0", 94 "eslint-config-airbnb-typescript": "^17.1.0",
95 "eslint-config-prettier": "^9.1.0", 95 "eslint-config-prettier": "^9.1.0",
96 "eslint-import-resolver-typescript": "^3.6.1", 96 "eslint-import-resolver-typescript": "^3.6.1",
97 "eslint-plugin-import": "^2.29.0", 97 "eslint-plugin-import": "^2.29.1",
98 "eslint-plugin-jsx-a11y": "^6.8.0", 98 "eslint-plugin-jsx-a11y": "^6.8.0",
99 "eslint-plugin-mobx": "^0.0.9", 99 "eslint-plugin-mobx": "^0.0.9",
100 "eslint-plugin-prettier": "^5.0.1", 100 "eslint-plugin-prettier": "^5.1.1",
101 "eslint-plugin-react": "^7.33.2", 101 "eslint-plugin-react": "^7.33.2",
102 "eslint-plugin-react-hooks": "^4.6.0", 102 "eslint-plugin-react-hooks": "^4.6.0",
103 "html-minifier-terser": "^7.2.0", 103 "html-minifier-terser": "^7.2.0",
104 "micromatch": "^4.0.5", 104 "micromatch": "^4.0.5",
105 "pnpapi": "^0.0.0", 105 "pnpapi": "^0.0.0",
106 "prettier": "^3.1.0", 106 "prettier": "^3.1.1",
107 "typescript": "5.3.2", 107 "typescript": "5.3.3",
108 "vite": "^5.0.4", 108 "vite": "^5.0.10",
109 "vite-plugin-pwa": "^0.17.2", 109 "vite-plugin-pwa": "^0.17.4",
110 "workbox-window": "^7.0.0" 110 "workbox-window": "^7.0.0"
111 } 111 }
112} 112}
diff --git a/subprojects/generator-cli/build.gradle.kts b/subprojects/generator-cli/build.gradle.kts
new file mode 100644
index 00000000..6c681222
--- /dev/null
+++ b/subprojects/generator-cli/build.gradle.kts
@@ -0,0 +1,24 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7plugins {
8 id("tools.refinery.gradle.java-application")
9}
10
11dependencies {
12 implementation(project(":refinery-generator"))
13 implementation(libs.jcommander)
14 implementation(libs.slf4j.api)
15}
16
17application {
18 mainClass.set("tools.refinery.generator.cli.RefineryCli")
19}
20
21tasks.shadowJar {
22 // Silence Xtext warning.
23 append("plugin.properties")
24}
diff --git a/subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/RefineryCli.java b/subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/RefineryCli.java
new file mode 100644
index 00000000..5de579e6
--- /dev/null
+++ b/subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/RefineryCli.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.generator.cli;
7
8import com.beust.jcommander.JCommander;
9import com.beust.jcommander.ParameterException;
10import com.google.inject.Inject;
11import tools.refinery.generator.cli.commands.GenerateCommand;
12import tools.refinery.generator.standalone.StandaloneRefinery;
13
14import java.io.IOException;
15
16public class RefineryCli {
17 private static final String GENERATE_COMMAND = "generate";
18
19 @Inject
20 private GenerateCommand generateCommand;
21
22 private JCommander jCommander;
23
24 public String parseArguments(String... args) {
25 var jc = getJCommander();
26 jc.parse(args);
27 return jc.getParsedCommand();
28 }
29
30 public void run(String command) throws IOException {
31 switch (command) {
32 case GENERATE_COMMAND -> generateCommand.run();
33 case null, default -> showUsageAndExit();
34 }
35 }
36
37 public void showUsageAndExit() {
38 getJCommander().usage();
39 System.exit(1);
40 }
41
42 private JCommander getJCommander() {
43 if (jCommander == null) {
44 jCommander = JCommander.newBuilder()
45 .programName("refinery")
46 .addObject(this)
47 .addCommand(GENERATE_COMMAND, generateCommand)
48 .build();
49 }
50 return jCommander;
51 }
52
53 public static void main(String[] args) throws IOException {
54 var cli = StandaloneRefinery.getInjector().getInstance(RefineryCli.class);
55 String command = null;
56 try {
57 command = cli.parseArguments(args);
58 } catch (ParameterException e) {
59 System.err.println(e.getMessage());
60 cli.showUsageAndExit();
61 }
62 cli.run(command);
63 }
64}
diff --git a/subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/commands/GenerateCommand.java b/subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/commands/GenerateCommand.java
new file mode 100644
index 00000000..b33fce23
--- /dev/null
+++ b/subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/commands/GenerateCommand.java
@@ -0,0 +1,87 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.generator.cli.commands;
7
8import com.beust.jcommander.Parameter;
9import com.beust.jcommander.Parameters;
10import com.google.inject.Inject;
11import org.eclipse.emf.ecore.resource.Resource;
12import tools.refinery.generator.ModelGeneratorFactory;
13import tools.refinery.generator.ProblemLoader;
14
15import java.io.FileOutputStream;
16import java.io.IOException;
17import java.util.ArrayList;
18import java.util.List;
19import java.util.Map;
20
21@Parameters(commandDescription = "Generate a model from a partial model")
22public class GenerateCommand {
23 @Inject
24 private ProblemLoader loader;
25
26 @Inject
27 private ModelGeneratorFactory generatorFactory;
28
29 private String inputPath;
30 private String outputPath = "-";
31 private List<String> scopes = new ArrayList<>();
32 private List<String> overrideScopes = new ArrayList<>();
33 private long randomSeed = 1;
34
35 @Parameter(description = "input path", required = true)
36 public void setInputPath(String inputPath) {
37 this.inputPath = inputPath;
38 }
39
40 @Parameter(names = {"-output", "-o"}, description = "Output path")
41 public void setOutputPath(String outputPath) {
42 this.outputPath = outputPath;
43 }
44
45 @Parameter(names = {"-scope", "-s"}, description = "Extra scope constraints")
46 public void setScopes(List<String> scopes) {
47 this.scopes = scopes;
48 }
49
50 @Parameter(names = {"-scope-override", "-S"}, description = "Override scope constraints")
51 public void setOverrideScopes(List<String> overrideScopes) {
52 this.overrideScopes = overrideScopes;
53 }
54
55 @Parameter(names = {"-random-seed", "-r"}, description = "Random seed")
56 public void setRandomSeed(long randomSeed) {
57 this.randomSeed = randomSeed;
58 }
59
60 public void run() throws IOException {
61 var problem = isStandardStream(inputPath) ? loader.loadStream(System.in) : loader.loadFile(inputPath);
62 problem = loader.loadScopeConstraints(problem, scopes, overrideScopes);
63 var generator = generatorFactory.createGenerator(problem);
64 generator.setRandomSeed(randomSeed);
65 generator.generate();
66 var solution = generator.serializeSolution();
67 var solutionResource = solution.eResource();
68 var saveOptions = Map.of();
69 if (isStandardStream(outputPath)) {
70 printSolution(solutionResource, saveOptions);
71 } else {
72 try (var outputStream = new FileOutputStream(outputPath)) {
73 solutionResource.save(outputStream, saveOptions);
74 }
75 }
76 }
77
78 private boolean isStandardStream(String path) {
79 return path == null || path.equals("-");
80 }
81
82 // We deliberately write to the standard output if no output path is specified.
83 @SuppressWarnings("squid:S106")
84 private void printSolution(Resource solutionResource, Map<?, ?> saveOptions) throws IOException {
85 solutionResource.save(System.out, saveOptions);
86 }
87}
diff --git a/subprojects/generator/build.gradle.kts b/subprojects/generator/build.gradle.kts
index d87ce6de..f78fee4d 100644
--- a/subprojects/generator/build.gradle.kts
+++ b/subprojects/generator/build.gradle.kts
@@ -12,4 +12,5 @@ dependencies {
12 api(project(":refinery-language-semantics")) 12 api(project(":refinery-language-semantics"))
13 api(libs.eclipseCollections.api) 13 api(libs.eclipseCollections.api)
14 implementation(project(":refinery-store-query-interpreter")) 14 implementation(project(":refinery-store-query-interpreter"))
15 testImplementation(testFixtures(project(":refinery-language")))
15} 16}
diff --git a/subprojects/generator/src/main/java/tools/refinery/generator/ModelGenerator.java b/subprojects/generator/src/main/java/tools/refinery/generator/ModelGenerator.java
index 5b44c10a..1515dceb 100644
--- a/subprojects/generator/src/main/java/tools/refinery/generator/ModelGenerator.java
+++ b/subprojects/generator/src/main/java/tools/refinery/generator/ModelGenerator.java
@@ -5,7 +5,10 @@
5 */ 5 */
6package tools.refinery.generator; 6package tools.refinery.generator;
7 7
8import com.google.inject.Provider;
9import tools.refinery.language.model.problem.Problem;
8import tools.refinery.language.semantics.ProblemTrace; 10import tools.refinery.language.semantics.ProblemTrace;
11import tools.refinery.language.semantics.SolutionSerializer;
9import tools.refinery.store.dse.strategy.BestFirstStoreManager; 12import tools.refinery.store.dse.strategy.BestFirstStoreManager;
10import tools.refinery.store.map.Version; 13import tools.refinery.store.map.Version;
11import tools.refinery.store.model.ModelStore; 14import tools.refinery.store.model.ModelStore;
@@ -16,21 +19,22 @@ import tools.refinery.store.reasoning.seed.ModelSeed;
16 19
17public class ModelGenerator extends ModelFacade { 20public class ModelGenerator extends ModelFacade {
18 private final Version initialVersion; 21 private final Version initialVersion;
19 22 private final Provider<SolutionSerializer> solutionSerializerProvider;
20 private int randomSeed = 0; 23 private long randomSeed = 1;
21
22 private boolean lastGenerationSuccessful; 24 private boolean lastGenerationSuccessful;
23 25
24 public ModelGenerator(ProblemTrace problemTrace, ModelStore store, ModelSeed modelSeed) { 26 ModelGenerator(ProblemTrace problemTrace, ModelStore store, ModelSeed modelSeed,
27 Provider<SolutionSerializer> solutionSerializerProvider) {
25 super(problemTrace, store, modelSeed, Concreteness.CANDIDATE); 28 super(problemTrace, store, modelSeed, Concreteness.CANDIDATE);
29 this.solutionSerializerProvider = solutionSerializerProvider;
26 initialVersion = getModel().commit(); 30 initialVersion = getModel().commit();
27 } 31 }
28 32
29 public int getRandomSeed() { 33 public long getRandomSeed() {
30 return randomSeed; 34 return randomSeed;
31 } 35 }
32 36
33 public void setRandomSeed(int randomSeed) { 37 public void setRandomSeed(long randomSeed) {
34 this.randomSeed = randomSeed; 38 this.randomSeed = randomSeed;
35 this.lastGenerationSuccessful = false; 39 this.lastGenerationSuccessful = false;
36 } 40 }
@@ -50,7 +54,7 @@ public class ModelGenerator extends ModelFacade {
50 if (solutions.isEmpty()) { 54 if (solutions.isEmpty()) {
51 return false; 55 return false;
52 } 56 }
53 getModel().restore(solutions.get(0).version()); 57 getModel().restore(solutions.getFirst().version());
54 lastGenerationSuccessful = true; 58 lastGenerationSuccessful = true;
55 return true; 59 return true;
56 } 60 }
@@ -63,9 +67,19 @@ public class ModelGenerator extends ModelFacade {
63 67
64 @Override 68 @Override
65 public <A, C> PartialInterpretation<A, C> getPartialInterpretation(PartialSymbol<A, C> partialSymbol) { 69 public <A, C> PartialInterpretation<A, C> getPartialInterpretation(PartialSymbol<A, C> partialSymbol) {
70 checkSuccessfulGeneration();
71 return super.getPartialInterpretation(partialSymbol);
72 }
73
74 public Problem serializeSolution() {
75 checkSuccessfulGeneration();
76 var serializer = solutionSerializerProvider.get();
77 return serializer.serializeSolution(getProblemTrace(), getModel());
78 }
79
80 private void checkSuccessfulGeneration() {
66 if (!lastGenerationSuccessful) { 81 if (!lastGenerationSuccessful) {
67 throw new IllegalStateException("No generated model is available"); 82 throw new IllegalStateException("No generated model is available");
68 } 83 }
69 return super.getPartialInterpretation(partialSymbol);
70 } 84 }
71} 85}
diff --git a/subprojects/generator/src/main/java/tools/refinery/generator/ModelGeneratorFactory.java b/subprojects/generator/src/main/java/tools/refinery/generator/ModelGeneratorFactory.java
index 6642d591..587601f2 100644
--- a/subprojects/generator/src/main/java/tools/refinery/generator/ModelGeneratorFactory.java
+++ b/subprojects/generator/src/main/java/tools/refinery/generator/ModelGeneratorFactory.java
@@ -9,6 +9,7 @@ import com.google.inject.Inject;
9import com.google.inject.Provider; 9import com.google.inject.Provider;
10import tools.refinery.language.model.problem.Problem; 10import tools.refinery.language.model.problem.Problem;
11import tools.refinery.language.semantics.ModelInitializer; 11import tools.refinery.language.semantics.ModelInitializer;
12import tools.refinery.language.semantics.SolutionSerializer;
12import tools.refinery.store.dse.propagation.PropagationAdapter; 13import tools.refinery.store.dse.propagation.PropagationAdapter;
13import tools.refinery.store.dse.transition.DesignSpaceExplorationAdapter; 14import tools.refinery.store.dse.transition.DesignSpaceExplorationAdapter;
14import tools.refinery.store.model.ModelStore; 15import tools.refinery.store.model.ModelStore;
@@ -25,6 +26,9 @@ public final class ModelGeneratorFactory {
25 @Inject 26 @Inject
26 private Provider<ModelInitializer> initializerProvider; 27 private Provider<ModelInitializer> initializerProvider;
27 28
29 @Inject
30 private Provider<SolutionSerializer> solutionSerializerProvider;
31
28 private CancellationToken cancellationToken = CancellationToken.NONE; 32 private CancellationToken cancellationToken = CancellationToken.NONE;
29 33
30 private boolean debugPartialInterpretations; 34 private boolean debugPartialInterpretations;
@@ -53,7 +57,8 @@ public final class ModelGeneratorFactory {
53 .requiredInterpretations(getRequiredInterpretations())); 57 .requiredInterpretations(getRequiredInterpretations()));
54 initializer.configureStoreBuilder(storeBuilder); 58 initializer.configureStoreBuilder(storeBuilder);
55 var store = storeBuilder.build(); 59 var store = storeBuilder.build();
56 return new ModelGenerator(initializer.getProblemTrace(), store, initializer.getModelSeed()); 60 return new ModelGenerator(initializer.getProblemTrace(), store, initializer.getModelSeed(),
61 solutionSerializerProvider);
57 } 62 }
58 63
59 private Collection<Concreteness> getRequiredInterpretations() { 64 private Collection<Concreteness> getRequiredInterpretations() {
diff --git a/subprojects/generator/src/main/java/tools/refinery/generator/ModelSemantics.java b/subprojects/generator/src/main/java/tools/refinery/generator/ModelSemantics.java
index bc02c887..7207a509 100644
--- a/subprojects/generator/src/main/java/tools/refinery/generator/ModelSemantics.java
+++ b/subprojects/generator/src/main/java/tools/refinery/generator/ModelSemantics.java
@@ -11,7 +11,7 @@ import tools.refinery.store.reasoning.literal.Concreteness;
11import tools.refinery.store.reasoning.seed.ModelSeed; 11import tools.refinery.store.reasoning.seed.ModelSeed;
12 12
13public class ModelSemantics extends ModelFacade { 13public class ModelSemantics extends ModelFacade {
14 public ModelSemantics(ProblemTrace problemTrace, ModelStore store, ModelSeed modelSeed) { 14 ModelSemantics(ProblemTrace problemTrace, ModelStore store, ModelSeed modelSeed) {
15 super(problemTrace, store, modelSeed, Concreteness.PARTIAL); 15 super(problemTrace, store, modelSeed, Concreteness.PARTIAL);
16 } 16 }
17} 17}
diff --git a/subprojects/generator/src/main/java/tools/refinery/generator/ProblemLoader.java b/subprojects/generator/src/main/java/tools/refinery/generator/ProblemLoader.java
index 29f80714..20ea8132 100644
--- a/subprojects/generator/src/main/java/tools/refinery/generator/ProblemLoader.java
+++ b/subprojects/generator/src/main/java/tools/refinery/generator/ProblemLoader.java
@@ -10,20 +10,30 @@ import com.google.inject.Provider;
10import org.eclipse.emf.common.util.URI; 10import org.eclipse.emf.common.util.URI;
11import org.eclipse.emf.ecore.resource.Resource; 11import org.eclipse.emf.ecore.resource.Resource;
12import org.eclipse.xtext.diagnostics.Severity; 12import org.eclipse.xtext.diagnostics.Severity;
13import org.eclipse.xtext.resource.FileExtensionProvider;
13import org.eclipse.xtext.resource.IResourceFactory; 14import org.eclipse.xtext.resource.IResourceFactory;
14import org.eclipse.xtext.resource.XtextResourceSet; 15import org.eclipse.xtext.resource.XtextResourceSet;
15import org.eclipse.xtext.util.LazyStringInputStream; 16import org.eclipse.xtext.util.LazyStringInputStream;
16import org.eclipse.xtext.validation.CheckMode; 17import org.eclipse.xtext.validation.CheckMode;
17import org.eclipse.xtext.validation.IResourceValidator; 18import org.eclipse.xtext.validation.IResourceValidator;
18import tools.refinery.language.model.problem.Problem; 19import tools.refinery.language.model.problem.Problem;
20import tools.refinery.language.model.problem.Relation;
21import tools.refinery.language.model.problem.ScopeDeclaration;
19import tools.refinery.store.util.CancellationToken; 22import tools.refinery.store.util.CancellationToken;
20 23
24import java.io.ByteArrayOutputStream;
21import java.io.File; 25import java.io.File;
22import java.io.IOException; 26import java.io.IOException;
23import java.io.InputStream; 27import java.io.InputStream;
28import java.nio.charset.StandardCharsets;
29import java.util.ArrayList;
30import java.util.HashSet;
31import java.util.List;
24import java.util.Map; 32import java.util.Map;
25 33
26public class ProblemLoader { 34public class ProblemLoader {
35 private String fileExtension;
36
27 @Inject 37 @Inject
28 private Provider<XtextResourceSet> resourceSetProvider; 38 private Provider<XtextResourceSet> resourceSetProvider;
29 39
@@ -35,25 +45,39 @@ public class ProblemLoader {
35 45
36 private CancellationToken cancellationToken = CancellationToken.NONE; 46 private CancellationToken cancellationToken = CancellationToken.NONE;
37 47
48 @Inject
49 public void setFileExtensionProvider(FileExtensionProvider fileExtensionProvider) {
50 this.fileExtension = fileExtensionProvider.getPrimaryFileExtension();
51 }
52
38 public ProblemLoader cancellationToken(CancellationToken cancellationToken) { 53 public ProblemLoader cancellationToken(CancellationToken cancellationToken) {
39 this.cancellationToken = cancellationToken; 54 this.cancellationToken = cancellationToken;
40 return this; 55 return this;
41 } 56 }
42 57
43 public Problem loadString(String problemString) throws IOException { 58 public Problem loadString(String problemString, URI uri) throws IOException {
44 try (var stream = new LazyStringInputStream(problemString)) { 59 try (var stream = new LazyStringInputStream(problemString)) {
45 return loadStream(stream); 60 return loadStream(stream, uri);
46 } 61 }
47 } 62 }
48 63
49 public Problem loadStream(InputStream inputStream) throws IOException { 64 public Problem loadString(String problemString) throws IOException {
65 return loadString(problemString, null);
66 }
67
68 public Problem loadStream(InputStream inputStream, URI uri) throws IOException {
50 var resourceSet = resourceSetProvider.get(); 69 var resourceSet = resourceSetProvider.get();
51 var resource = resourceFactory.createResource(URI.createFileURI("__synthetic.problem")); 70 var resourceUri = uri == null ? URI.createFileURI("__synthetic." + fileExtension) : uri;
71 var resource = resourceFactory.createResource(resourceUri);
52 resourceSet.getResources().add(resource); 72 resourceSet.getResources().add(resource);
53 resource.load(inputStream, Map.of()); 73 resource.load(inputStream, Map.of());
54 return loadResource(resource); 74 return loadResource(resource);
55 } 75 }
56 76
77 public Problem loadStream(InputStream inputStream) throws IOException {
78 return loadStream(inputStream, null);
79 }
80
57 public Problem loadFile(File file) throws IOException { 81 public Problem loadFile(File file) throws IOException {
58 return loadFile(file.getAbsolutePath()); 82 return loadFile(file.getAbsolutePath());
59 } 83 }
@@ -82,9 +106,63 @@ public class ProblemLoader {
82 if (!errors.isEmpty()) { 106 if (!errors.isEmpty()) {
83 throw new ValidationErrorsException(resource.getURI(), errors); 107 throw new ValidationErrorsException(resource.getURI(), errors);
84 } 108 }
85 if (resource.getContents().isEmpty() || !(resource.getContents().get(0) instanceof Problem problem)) { 109 if (resource.getContents().isEmpty() || !(resource.getContents().getFirst() instanceof Problem problem)) {
86 throw new IllegalArgumentException("Model generation problem not found in resource " + resource.getURI()); 110 throw new IllegalArgumentException("Model generation problem not found in resource " + resource.getURI());
87 } 111 }
88 return problem; 112 return problem;
89 } 113 }
114
115 public Problem loadScopeConstraints(Problem problem, List<String> extraScopes,
116 List<String> overrideScopes) throws IOException {
117 var allScopes = new ArrayList<>(extraScopes);
118 allScopes.addAll(overrideScopes);
119 if (allScopes.isEmpty()) {
120 return problem;
121 }
122 int originalStatementCount = problem.getStatements().size();
123 var builder = new StringBuilder();
124 var problemResource = problem.eResource();
125 try (var outputStream = new ByteArrayOutputStream()) {
126 problemResource.save(outputStream, Map.of());
127 builder.append(outputStream.toString(StandardCharsets.UTF_8));
128 }
129 builder.append('\n');
130 for (var scope : allScopes) {
131 builder.append("scope ").append(scope).append(".\n");
132 }
133 var modifiedProblem = loadString(builder.toString(), problemResource.getURI());
134 var modifiedStatements = modifiedProblem.getStatements();
135 int modifiedStatementCount = modifiedStatements.size();
136 if (modifiedStatementCount != originalStatementCount + allScopes.size()) {
137 throw new IllegalArgumentException("Failed to parse scope constraints");
138 }
139 // Override scopes remove any scope constraint from the original problem with the same target type.
140 var overriddenScopes = new HashSet<Relation>();
141 for (int i = modifiedStatementCount - overrideScopes.size(); i < modifiedStatementCount; i++) {
142 var statement = modifiedStatements.get(i);
143 if (!(statement instanceof ScopeDeclaration scopeDeclaration)) {
144 throw new IllegalStateException("Invalid scope constraint: " + statement);
145 }
146 for (var typeScope : scopeDeclaration.getTypeScopes()) {
147 overriddenScopes.add(typeScope.getTargetType());
148 }
149 }
150 int statementIndex = 0;
151 var iterator = modifiedStatements.iterator();
152 // Scope overrides only affect type scopes from the original problem and leave type scopes added on the
153 // command line intact.
154 while (statementIndex < originalStatementCount && iterator.hasNext()) {
155 var statement = iterator.next();
156 if (statement instanceof ScopeDeclaration scopeDeclaration) {
157 var typeScopes = scopeDeclaration.getTypeScopes();
158 typeScopes.removeIf(typeScope -> overriddenScopes.contains(typeScope.getTargetType()));
159 // Scope declarations with no type scopes are invalid, so we have to remove them.
160 if (typeScopes.isEmpty()) {
161 iterator.remove();
162 }
163 }
164 statementIndex++;
165 }
166 return modifiedProblem;
167 }
90} 168}
diff --git a/subprojects/generator/src/test/java/tools/refinery/generator/ProblemLoaderTest.java b/subprojects/generator/src/test/java/tools/refinery/generator/ProblemLoaderTest.java
new file mode 100644
index 00000000..0c0db105
--- /dev/null
+++ b/subprojects/generator/src/test/java/tools/refinery/generator/ProblemLoaderTest.java
@@ -0,0 +1,129 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.generator;
7
8
9import com.google.inject.Inject;
10import org.eclipse.xtext.testing.InjectWith;
11import org.eclipse.xtext.testing.extensions.InjectionExtension;
12import org.junit.jupiter.api.extension.ExtendWith;
13import org.junit.jupiter.params.ParameterizedTest;
14import org.junit.jupiter.params.provider.Arguments;
15import org.junit.jupiter.params.provider.MethodSource;
16import tools.refinery.language.tests.ProblemInjectorProvider;
17
18import java.io.ByteArrayOutputStream;
19import java.io.IOException;
20import java.nio.charset.StandardCharsets;
21import java.util.List;
22import java.util.Map;
23import java.util.stream.Stream;
24
25import static org.hamcrest.MatcherAssert.assertThat;
26import static org.hamcrest.Matchers.is;
27
28@ExtendWith(InjectionExtension.class)
29@InjectWith(ProblemInjectorProvider.class)
30class ProblemLoaderTest {
31 private static final String PREFIX = """
32 class Foo.
33 class Bar.
34 """;
35
36 @Inject
37 private ProblemLoader loader;
38
39 @ParameterizedTest
40 @MethodSource
41 void loadScopeConstraintsTest(String originalScopes, List<String> scopes, List<String> overrideScopes,
42 String expectedScopes) throws IOException {
43 var problem = loader.loadString(PREFIX + originalScopes);
44 var modifiedProblem = loader.loadScopeConstraints(problem, scopes, overrideScopes);
45 String serializedProblem;
46 try (var outputStream = new ByteArrayOutputStream()) {
47 modifiedProblem.eResource().save(outputStream, Map.of());
48 serializedProblem = outputStream.toString(StandardCharsets.UTF_8);
49 }
50 assertThat(serializedProblem, is(PREFIX + expectedScopes));
51 }
52
53 static Stream<Arguments> loadScopeConstraintsTest() {
54 return Stream.of(Arguments.of("",
55 List.of(),
56 List.of(),
57 ""), Arguments.of("",
58 List.of("node=5..10"),
59 List.of(), """
60
61 scope node=5..10.
62 """), Arguments.of("",
63 List.of("Foo=2", "Bar=3"),
64 List.of(), """
65
66 scope Foo=2.
67 scope Bar=3.
68 """), Arguments.of("""
69 scope Foo = 1, Bar = 1.
70 """,
71 List.of("node=5..10"),
72 List.of(), """
73 scope Foo = 1, Bar = 1.
74
75 scope node=5..10.
76 """), Arguments.of("""
77 scope Foo = 0..10, Bar = 1.
78 """,
79 List.of("Foo = 5"),
80 List.of(), """
81 scope Foo = 0..10, Bar = 1.
82
83 scope Foo = 5.
84 """), Arguments.of("""
85 scope Foo = 1, Bar = 1.
86 """,
87 List.of(),
88 List.of("node=5..10"), """
89 scope Foo = 1, Bar = 1.
90
91 scope node=5..10.
92 """), Arguments.of("""
93 scope Foo = 1, Bar = 1.
94 """,
95 List.of(),
96 List.of("Foo=3..4"), """
97 scope Bar = 1.
98
99 scope Foo=3..4.
100 """), Arguments.of("""
101 scope Foo = 1, Bar = 1.
102 """,
103 List.of("Foo=2"),
104 List.of("Foo=3..4"), """
105 scope Bar = 1.
106
107 scope Foo=2.
108 scope Foo=3..4.
109 """), Arguments.of("""
110 scope Foo = 1.
111 scope Bar = 1.
112 """,
113 List.of(),
114 List.of("Bar=3..4"), """
115 scope Foo = 1.
116
117
118 scope Bar=3..4.
119 """), Arguments.of("""
120 scope Foo = 1, Bar = 1.
121 """,
122 List.of(),
123 List.of("Foo=3..4", "Bar=4..5"), """
124
125 scope Foo=3..4.
126 scope Bar=4..5.
127 """));
128 }
129}
diff --git a/subprojects/language-model/src/main/resources/model/problem.ecore b/subprojects/language-model/src/main/resources/model/problem.ecore
index 204f922c..74229a89 100644
--- a/subprojects/language-model/src/main/resources/model/problem.ecore
+++ b/subprojects/language-model/src/main/resources/model/problem.ecore
@@ -23,7 +23,7 @@
23 <eStructuralFeatures xsi:type="ecore:EAttribute" name="kind" eType="#//ReferenceKind"/> 23 <eStructuralFeatures xsi:type="ecore:EAttribute" name="kind" eType="#//ReferenceKind"/>
24 <eStructuralFeatures xsi:type="ecore:EReference" name="referenceType" eType="#//Relation"/> 24 <eStructuralFeatures xsi:type="ecore:EReference" name="referenceType" eType="#//Relation"/>
25 <eStructuralFeatures xsi:type="ecore:EReference" name="invalidMultiplicity" eType="#//Relation" 25 <eStructuralFeatures xsi:type="ecore:EReference" name="invalidMultiplicity" eType="#//Relation"
26 containment="true"/> 26 transient="true" containment="true"/>
27 </eClassifiers> 27 </eClassifiers>
28 <eClassifiers xsi:type="ecore:EClass" name="NamedElement" abstract="true"> 28 <eClassifiers xsi:type="ecore:EClass" name="NamedElement" abstract="true">
29 <eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/> 29 <eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
diff --git a/subprojects/language-semantics/build.gradle.kts b/subprojects/language-semantics/build.gradle.kts
index 689f621e..777ef993 100644
--- a/subprojects/language-semantics/build.gradle.kts
+++ b/subprojects/language-semantics/build.gradle.kts
@@ -16,6 +16,7 @@ dependencies {
16 api(project(":refinery-store-reasoning")) 16 api(project(":refinery-store-reasoning"))
17 implementation(project(":refinery-store-reasoning-scope")) 17 implementation(project(":refinery-store-reasoning-scope"))
18 runtimeOnly(libs.eclipseCollections) 18 runtimeOnly(libs.eclipseCollections)
19 testImplementation(project(":refinery-generator"))
19 testImplementation(project(":refinery-store-dse-visualization")) 20 testImplementation(project(":refinery-store-dse-visualization"))
20 testImplementation(project(":refinery-store-query-interpreter")) 21 testImplementation(project(":refinery-store-query-interpreter"))
21 testImplementation(testFixtures(project(":refinery-language"))) 22 testImplementation(testFixtures(project(":refinery-language")))
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ModelInitializer.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ModelInitializer.java
index ecaa7e0d..38bf5a61 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ModelInitializer.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ModelInitializer.java
@@ -27,9 +27,7 @@ import tools.refinery.store.reasoning.seed.ModelSeed;
27import tools.refinery.store.reasoning.seed.Seed; 27import tools.refinery.store.reasoning.seed.Seed;
28import tools.refinery.store.reasoning.translator.TranslationException; 28import tools.refinery.store.reasoning.translator.TranslationException;
29import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator; 29import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator;
30import tools.refinery.store.reasoning.translator.metamodel.Metamodel; 30import tools.refinery.store.reasoning.translator.metamodel.*;
31import tools.refinery.store.reasoning.translator.metamodel.MetamodelBuilder;
32import tools.refinery.store.reasoning.translator.metamodel.MetamodelTranslator;
33import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator; 31import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
34import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity; 32import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity;
35import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; 33import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
@@ -73,6 +71,10 @@ public class ModelInitializer {
73 71
74 private ScopePropagator scopePropagator; 72 private ScopePropagator scopePropagator;
75 73
74 private int nodeCount;
75
76 private ModelSeed.Builder modelSeedBuilder;
77
76 private ModelSeed modelSeed; 78 private ModelSeed modelSeed;
77 79
78 public void readProblem(Problem problem) { 80 public void readProblem(Problem problem) {
@@ -101,13 +103,17 @@ public class ModelInitializer {
101 TruthValue.FALSE)); 103 TruthValue.FALSE));
102 collectNodes(); 104 collectNodes();
103 collectPartialSymbols(); 105 collectPartialSymbols();
106 nodeCount = problemTrace.getNodeTrace().size();
107 modelSeedBuilder = ModelSeed.builder(nodeCount);
108 collectAssertions();
104 collectMetamodel(); 109 collectMetamodel();
105 metamodel = metamodelBuilder.build(); 110 metamodel = metamodelBuilder.build();
106 problemTrace.setMetamodel(metamodel); 111 problemTrace.setMetamodel(metamodel);
107 collectAssertions(); 112 fixClassDeclarationAssertions();
108 int nodeCount = problemTrace.getNodeTrace().size();
109 var modelSeedBuilder = ModelSeed.builder(nodeCount);
110 for (var entry : relationInfoMap.entrySet()) { 113 for (var entry : relationInfoMap.entrySet()) {
114 if (entry.getKey() instanceof ReferenceDeclaration) {
115 continue;
116 }
111 var info = entry.getValue(); 117 var info = entry.getValue();
112 var partialRelation = info.partialRelation(); 118 var partialRelation = info.partialRelation();
113 modelSeedBuilder.seed(partialRelation, info.toSeed(nodeCount)); 119 modelSeedBuilder.seed(partialRelation, info.toSeed(nodeCount));
@@ -296,7 +302,20 @@ public class ModelInitializer {
296 } 302 }
297 var multiplicity = getMultiplicityConstraint(referenceDeclaration); 303 var multiplicity = getMultiplicityConstraint(referenceDeclaration);
298 try { 304 try {
299 metamodelBuilder.reference(relation, source, containment, multiplicity, target, oppositeRelation); 305 var seed = relationInfoMap.get(referenceDeclaration).toSeed(nodeCount);
306 var defaultValue = seed.majorityValue();
307 if (defaultValue.must()) {
308 defaultValue = TruthValue.FALSE;
309 }
310 modelSeedBuilder.seed(relation, seed);
311 metamodelBuilder.reference(relation, ReferenceInfo.builder()
312 .containment(containment)
313 .source(source)
314 .multiplicity(multiplicity)
315 .target(target)
316 .opposite(oppositeRelation)
317 .defaultValue(defaultValue)
318 .build());
300 } catch (RuntimeException e) { 319 } catch (RuntimeException e) {
301 throw TracedException.addTrace(classDeclaration, e); 320 throw TracedException.addTrace(classDeclaration, e);
302 } 321 }
@@ -355,10 +374,6 @@ public class ModelInitializer {
355 collectCardinalityAssertions(newNodeId, TruthValue.UNKNOWN); 374 collectCardinalityAssertions(newNodeId, TruthValue.UNKNOWN);
356 var tuple = Tuple.of(newNodeId); 375 var tuple = Tuple.of(newNodeId);
357 mergeValue(classDeclaration, tuple, TruthValue.TRUE); 376 mergeValue(classDeclaration, tuple, TruthValue.TRUE);
358 var typeInfo = metamodel.typeHierarchy().getAnalysisResult(getPartialRelation(classDeclaration));
359 for (var subType : typeInfo.getDirectSubtypes()) {
360 partialRelationInfoMap.get(subType).assertions().mergeValue(tuple, TruthValue.FALSE);
361 }
362 } 377 }
363 378
364 private void collectEnumAssertions(EnumDeclaration enumDeclaration) { 379 private void collectEnumAssertions(EnumDeclaration enumDeclaration) {
@@ -399,6 +414,27 @@ public class ModelInitializer {
399 } 414 }
400 } 415 }
401 416
417 private void fixClassDeclarationAssertions() {
418 for (var statement : problem.getStatements()) {
419 if (statement instanceof ClassDeclaration classDeclaration) {
420 fixClassDeclarationAssertions(classDeclaration);
421 }
422 }
423 }
424
425 private void fixClassDeclarationAssertions(ClassDeclaration classDeclaration) {
426 var newNode = classDeclaration.getNewNode();
427 if (newNode == null) {
428 return;
429 }
430 var newNodeId = getNodeId(newNode);
431 var tuple = Tuple.of(newNodeId);
432 var typeInfo = metamodel.typeHierarchy().getAnalysisResult(getPartialRelation(classDeclaration));
433 for (var subType : typeInfo.getDirectSubtypes()) {
434 partialRelationInfoMap.get(subType).assertions().mergeValue(tuple, TruthValue.FALSE);
435 }
436 }
437
402 private void mergeValue(Relation relation, Tuple key, TruthValue value) { 438 private void mergeValue(Relation relation, Tuple key, TruthValue value) {
403 getRelationInfo(relation).assertions().mergeValue(key, value); 439 getRelationInfo(relation).assertions().mergeValue(key, value);
404 } 440 }
@@ -482,7 +518,7 @@ public class ModelInitializer {
482 defaultValue = TruthValue.FALSE; 518 defaultValue = TruthValue.FALSE;
483 } else { 519 } else {
484 var seed = modelSeed.getSeed(partialRelation); 520 var seed = modelSeed.getSeed(partialRelation);
485 defaultValue = seed.reducedValue() == TruthValue.FALSE ? TruthValue.FALSE : TruthValue.UNKNOWN; 521 defaultValue = seed.majorityValue() == TruthValue.FALSE ? TruthValue.FALSE : TruthValue.UNKNOWN;
486 var cursor = seed.getCursor(defaultValue, problemTrace.getNodeTrace().size()); 522 var cursor = seed.getCursor(defaultValue, problemTrace.getNodeTrace().size());
487 // The symbol should be mutable if there is at least one non-default entry in the seed. 523 // The symbol should be mutable if there is at least one non-default entry in the seed.
488 mutable = mutable || cursor.move(); 524 mutable = mutable || cursor.move();
@@ -495,7 +531,7 @@ public class ModelInitializer {
495 var problemParameters = predicateDefinition.getParameters(); 531 var problemParameters = predicateDefinition.getParameters();
496 int arity = problemParameters.size(); 532 int arity = problemParameters.size();
497 var parameters = new NodeVariable[arity]; 533 var parameters = new NodeVariable[arity];
498 var parameterMap = new HashMap<tools.refinery.language.model.problem.Variable, Variable>(arity); 534 var parameterMap = HashMap.<tools.refinery.language.model.problem.Variable, Variable>newHashMap(arity);
499 var commonLiterals = new ArrayList<Literal>(); 535 var commonLiterals = new ArrayList<Literal>();
500 for (int i = 0; i < arity; i++) { 536 for (int i = 0; i < arity; i++) {
501 var problemParameter = problemParameters.get(i); 537 var problemParameter = problemParameters.get(i);
@@ -532,7 +568,7 @@ public class ModelInitializer {
532 return existing; 568 return existing;
533 } 569 }
534 int localScopeSize = existing.size() + newVariables.size(); 570 int localScopeSize = existing.size() + newVariables.size();
535 var localScope = new HashMap<tools.refinery.language.model.problem.Variable, Variable>(localScopeSize); 571 var localScope = HashMap.<tools.refinery.language.model.problem.Variable, Variable>newHashMap(localScopeSize);
536 localScope.putAll(existing); 572 localScope.putAll(existing);
537 for (var newVariable : newVariables) { 573 for (var newVariable : newVariables) {
538 localScope.put(newVariable, Variable.of(newVariable.getName())); 574 localScope.put(newVariable, Variable.of(newVariable.getName()));
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ProblemTraceImpl.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ProblemTraceImpl.java
index cc634949..f686e980 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ProblemTraceImpl.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ProblemTraceImpl.java
@@ -9,7 +9,6 @@ import com.google.inject.Inject;
9import org.eclipse.collections.api.factory.primitive.ObjectIntMaps; 9import org.eclipse.collections.api.factory.primitive.ObjectIntMaps;
10import org.eclipse.collections.api.map.primitive.MutableObjectIntMap; 10import org.eclipse.collections.api.map.primitive.MutableObjectIntMap;
11import org.eclipse.collections.api.map.primitive.ObjectIntMap; 11import org.eclipse.collections.api.map.primitive.ObjectIntMap;
12import org.eclipse.emf.ecore.util.EcoreUtil;
13import org.eclipse.xtext.naming.IQualifiedNameConverter; 12import org.eclipse.xtext.naming.IQualifiedNameConverter;
14import org.eclipse.xtext.naming.QualifiedName; 13import org.eclipse.xtext.naming.QualifiedName;
15import org.eclipse.xtext.scoping.IScope; 14import org.eclipse.xtext.scoping.IScope;
@@ -28,7 +27,7 @@ import java.util.HashMap;
28import java.util.LinkedHashMap; 27import java.util.LinkedHashMap;
29import java.util.Map; 28import java.util.Map;
30 29
31public class ProblemTraceImpl implements ProblemTrace { 30class ProblemTraceImpl implements ProblemTrace {
32 @Inject 31 @Inject
33 private IQualifiedNameConverter qualifiedNameConverter; 32 private IQualifiedNameConverter qualifiedNameConverter;
34 33
@@ -164,24 +163,6 @@ public class ProblemTraceImpl implements ProblemTrace {
164 } 163 }
165 164
166 private <T> T getElement(IScope scope, QualifiedName qualifiedName, Class<T> type) { 165 private <T> T getElement(IScope scope, QualifiedName qualifiedName, Class<T> type) {
167 var iterator = scope.getElements(qualifiedName).iterator(); 166 return semanticsUtils.getElement(problem, scope, qualifiedName, type);
168 if (!iterator.hasNext()) {
169 var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName);
170 throw new IllegalArgumentException("No such %s: %s"
171 .formatted(type.getName(), qualifiedNameString));
172 }
173 var eObjectDescription = iterator.next();
174 if (iterator.hasNext()) {
175 var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName);
176 throw new IllegalArgumentException("Ambiguous %s: %s"
177 .formatted(type.getName(), qualifiedNameString));
178 }
179 var eObject = EcoreUtil.resolve(eObjectDescription.getEObjectOrProxy(), getProblem());
180 if (!type.isInstance(eObject)) {
181 var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName);
182 throw new IllegalArgumentException("Not a %s: %s"
183 .formatted(type.getName(), qualifiedNameString));
184 }
185 return type.cast(eObject);
186 } 167 }
187} 168}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SemanticsUtils.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SemanticsUtils.java
index b195a8e7..b72ba697 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SemanticsUtils.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SemanticsUtils.java
@@ -8,8 +8,14 @@ package tools.refinery.language.semantics;
8import com.google.inject.Inject; 8import com.google.inject.Inject;
9import com.google.inject.Singleton; 9import com.google.inject.Singleton;
10import org.eclipse.emf.ecore.EObject; 10import org.eclipse.emf.ecore.EObject;
11import org.eclipse.emf.ecore.util.EcoreUtil;
11import org.eclipse.xtext.naming.IQualifiedNameConverter; 12import org.eclipse.xtext.naming.IQualifiedNameConverter;
12import org.eclipse.xtext.naming.IQualifiedNameProvider; 13import org.eclipse.xtext.naming.IQualifiedNameProvider;
14import org.eclipse.xtext.naming.QualifiedName;
15import org.eclipse.xtext.scoping.IScope;
16import org.jetbrains.annotations.NotNull;
17import org.jetbrains.annotations.Nullable;
18import tools.refinery.language.model.problem.Problem;
13 19
14import java.util.Optional; 20import java.util.Optional;
15 21
@@ -28,4 +34,39 @@ public class SemanticsUtils {
28 } 34 }
29 return Optional.of(qualifiedNameConverter.toString(qualifiedName)); 35 return Optional.of(qualifiedNameConverter.toString(qualifiedName));
30 } 36 }
37
38 @Nullable
39 public <T> T maybeGetElement(Problem problem, IScope scope, QualifiedName qualifiedName, Class<T> type) {
40 if (qualifiedName == null) {
41 throw new IllegalArgumentException("Element name must not be null");
42 }
43 var iterator = scope.getElements(qualifiedName).iterator();
44 if (!iterator.hasNext()) {
45 return null;
46 }
47 var eObjectDescription = iterator.next();
48 if (iterator.hasNext()) {
49 var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName);
50 throw new IllegalArgumentException("Ambiguous %s: %s"
51 .formatted(type.getName(), qualifiedNameString));
52 }
53 var eObject = EcoreUtil.resolve(eObjectDescription.getEObjectOrProxy(), problem);
54 if (!type.isInstance(eObject)) {
55 var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName);
56 throw new IllegalArgumentException("Not a %s: %s"
57 .formatted(type.getName(), qualifiedNameString));
58 }
59 return type.cast(eObject);
60 }
61
62 @NotNull
63 public <T> T getElement(Problem problem, IScope scope, QualifiedName qualifiedName, Class<T> type) {
64 var element = maybeGetElement(problem, scope, qualifiedName, type);
65 if (element == null) {
66 var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName);
67 throw new IllegalArgumentException("No such %s: %s"
68 .formatted(type.getName(), qualifiedNameString));
69 }
70 return element;
71 }
31} 72}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java
new file mode 100644
index 00000000..57af599e
--- /dev/null
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java
@@ -0,0 +1,335 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.semantics;
7
8import com.google.inject.Inject;
9import com.google.inject.Provider;
10import org.eclipse.collections.api.factory.primitive.IntObjectMaps;
11import org.eclipse.collections.api.factory.primitive.ObjectIntMaps;
12import org.eclipse.collections.api.map.primitive.MutableIntObjectMap;
13import org.eclipse.collections.api.map.primitive.MutableObjectIntMap;
14import org.eclipse.emf.common.util.URI;
15import org.eclipse.emf.ecore.util.EcoreUtil;
16import org.eclipse.xtext.naming.IQualifiedNameConverter;
17import org.eclipse.xtext.naming.IQualifiedNameProvider;
18import org.eclipse.xtext.naming.QualifiedName;
19import org.eclipse.xtext.resource.FileExtensionProvider;
20import org.eclipse.xtext.resource.IResourceFactory;
21import org.eclipse.xtext.resource.XtextResource;
22import org.eclipse.xtext.resource.XtextResourceSet;
23import org.eclipse.xtext.scoping.IScopeProvider;
24import tools.refinery.language.model.problem.*;
25import tools.refinery.language.utils.ProblemDesugarer;
26import tools.refinery.language.utils.ProblemUtil;
27import tools.refinery.store.model.Model;
28import tools.refinery.store.reasoning.ReasoningAdapter;
29import tools.refinery.store.reasoning.interpretation.PartialInterpretation;
30import tools.refinery.store.reasoning.literal.Concreteness;
31import tools.refinery.store.reasoning.representation.PartialRelation;
32import tools.refinery.store.reasoning.translator.typehierarchy.InferredType;
33import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchyTranslator;
34import tools.refinery.store.representation.TruthValue;
35import tools.refinery.store.tuple.Tuple;
36
37import java.io.ByteArrayInputStream;
38import java.io.ByteArrayOutputStream;
39import java.io.IOException;
40import java.util.Locale;
41import java.util.Map;
42import java.util.TreeMap;
43import java.util.TreeSet;
44import java.util.function.Function;
45import java.util.stream.Collectors;
46
47public class SolutionSerializer {
48 private String fileExtension;
49
50 @Inject
51 private Provider<XtextResourceSet> resourceSetProvider;
52
53 @Inject
54 private IResourceFactory resourceFactory;
55
56 @Inject
57 private IQualifiedNameProvider qualifiedNameProvider;
58
59 @Inject
60 private IQualifiedNameConverter qualifiedNameConverter;
61
62 @Inject
63 private SemanticsUtils semanticsUtils;
64
65 @Inject
66 private IScopeProvider scopeProvider;
67
68 @Inject
69 private ProblemDesugarer desugarer;
70
71 private ProblemTrace trace;
72 private Model model;
73 private ReasoningAdapter reasoningAdapter;
74 private PartialInterpretation<TruthValue, Boolean> existsInterpretation;
75 private Problem problem;
76 private final MutableIntObjectMap<Node> nodes = IntObjectMaps.mutable.empty();
77
78 @Inject
79 public void setFileExtensionProvider(FileExtensionProvider fileExtensionProvider) {
80 this.fileExtension = fileExtensionProvider.getPrimaryFileExtension();
81 }
82
83 public Problem serializeSolution(ProblemTrace trace, Model model) {
84 var uri = URI.createFileURI("__synthetic" + fileExtension);
85 return serializeSolution(trace, model, uri);
86 }
87
88 public Problem serializeSolution(ProblemTrace trace, Model model, URI uri) {
89 this.trace = trace;
90 this.model = model;
91 reasoningAdapter = model.getAdapter(ReasoningAdapter.class);
92 existsInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE,
93 ReasoningAdapter.EXISTS_SYMBOL);
94 var originalProblem = trace.getProblem();
95 problem = copyProblem(originalProblem, uri);
96 problem.getStatements().removeIf(SolutionSerializer::shouldRemoveStatement);
97 problem.getNodes().removeIf(this::shouldRemoveNode);
98 addExistsAssertions();
99 addClassAssertions();
100 addReferenceAssertions();
101 return problem;
102 }
103
104 private static boolean shouldRemoveStatement(Statement statement) {
105 return statement instanceof Assertion || statement instanceof ScopeDeclaration;
106 }
107
108 private boolean shouldRemoveNode(Node newNode) {
109 var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(newNode);
110 var scope = scopeProvider.getScope(trace.getProblem(), ProblemPackage.Literals.ASSERTION__RELATION);
111 var originalNode = semanticsUtils.maybeGetElement(problem, scope, qualifiedName, Node.class);
112 if (originalNode == null) {
113 return false;
114 }
115 int nodeId = trace.getNodeId(originalNode);
116 return !isExistingNode(nodeId);
117 }
118
119 private boolean isExistingNode(int nodeId) {
120 var exists = existsInterpretation.get(Tuple.of(nodeId));
121 if (!exists.isConcrete()) {
122 throw new IllegalStateException("Invalid EXISTS %s for node %d".formatted(exists, nodeId));
123 }
124 return exists.may();
125 }
126
127 private Problem copyProblem(Problem originalProblem, URI uri) {
128 var newResourceSet = resourceSetProvider.get();
129 if (!fileExtension.equals(uri.fileExtension())) {
130 uri = uri.appendFileExtension(fileExtension);
131 }
132 var newResource = resourceFactory.createResource(uri);
133 newResourceSet.getResources().add(newResource);
134 var originalResource = originalProblem.eResource();
135 if (originalResource instanceof XtextResource) {
136 byte[] bytes;
137 try {
138 try (var outputStream = new ByteArrayOutputStream()) {
139 originalResource.save(outputStream, Map.of());
140 bytes = outputStream.toByteArray();
141 }
142 try (var inputStream = new ByteArrayInputStream(bytes)) {
143 newResource.load(inputStream, Map.of());
144 }
145 } catch (IOException e) {
146 throw new IllegalStateException("Failed to copy problem", e);
147 }
148 var contents = newResource.getContents();
149 if (!contents.isEmpty() && contents.getFirst() instanceof Problem newProblem) {
150 return newProblem;
151 }
152 throw new IllegalStateException("Invalid contents of copied problem");
153 } else {
154 var newProblem = EcoreUtil.copy(originalProblem);
155 newResource.getContents().add(newProblem);
156 return newProblem;
157 }
158 }
159
160 private Relation findRelation(Relation originalRelation) {
161 var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(originalRelation);
162 var scope = scopeProvider.getScope(problem, ProblemPackage.Literals.ASSERTION__RELATION);
163 return semanticsUtils.getElement(problem, scope, qualifiedName, Relation.class);
164 }
165
166 private Relation findPartialRelation(PartialRelation partialRelation) {
167 return findRelation(trace.getRelation(partialRelation));
168 }
169
170 private Node findNode(Node originalNode) {
171 var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(originalNode);
172 return findNode(qualifiedName);
173 }
174
175 private Node findNode(String name) {
176 var qualifiedName = qualifiedNameConverter.toQualifiedName(name);
177 return findNode(qualifiedName);
178 }
179
180 private Node findNode(QualifiedName qualifiedName) {
181 var scope = scopeProvider.getScope(problem, ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE);
182 return semanticsUtils.maybeGetElement(problem, scope, qualifiedName, Node.class);
183 }
184
185 private void addAssertion(Relation relation, LogicValue value, Node... arguments) {
186 var assertion = ProblemFactory.eINSTANCE.createAssertion();
187 assertion.setRelation(relation);
188 for (var node : arguments) {
189 var argument = ProblemFactory.eINSTANCE.createNodeAssertionArgument();
190 argument.setNode(node);
191 assertion.getArguments().add(argument);
192 }
193 var logicConstant = ProblemFactory.eINSTANCE.createLogicConstant();
194 logicConstant.setLogicValue(value);
195 assertion.setValue(logicConstant);
196 problem.getStatements().add(assertion);
197 }
198
199 private void addExistsAssertions() {
200 var builtinSymbols = desugarer.getBuiltinSymbols(problem)
201 .orElseThrow(() -> new IllegalStateException("No builtin library in copied problem"));
202 // Make sure to output exists assertions in a deterministic order.
203 var sortedNewNodes = new TreeMap<Integer, Node>();
204 for (var pair : trace.getNodeTrace().keyValuesView()) {
205 var originalNode = pair.getOne();
206 int nodeId = pair.getTwo();
207 var newNode = findNode(originalNode);
208 // Since all implicit nodes that do not exist has already been remove in serializeSolution,
209 // we only need to add !exists assertions to ::new nodes (nodes marked as an individual must always exist).
210 if (ProblemUtil.isNewNode(originalNode)) {
211 sortedNewNodes.put(nodeId, newNode);
212 } else {
213 nodes.put(nodeId, newNode);
214 }
215 }
216 for (var newNode : sortedNewNodes.values()) {
217 // If a node is a new node of the class, we should replace it with a normal node.
218 addAssertion(builtinSymbols.exists(), LogicValue.FALSE, newNode);
219 }
220 }
221
222 private void addClassAssertions() {
223 var types = trace.getMetamodel().typeHierarchy().getPreservedTypes().keySet().stream()
224 .collect(Collectors.toMap(Function.identity(), this::findPartialRelation));
225 var indexMap = ObjectIntMaps.mutable.empty();
226 var cursor = model.getInterpretation(TypeHierarchyTranslator.TYPE_SYMBOL).getAll();
227 while (cursor.move()) {
228 var key = cursor.getKey();
229 var nodeId = key.get(0);
230 if (isExistingNode(nodeId)) {
231 createNodeAndAssertType(nodeId, cursor.getValue(), types, indexMap);
232 }
233 }
234 }
235
236 private void createNodeAndAssertType(int nodeId, InferredType inferredType, Map<PartialRelation, Relation> types,
237 MutableObjectIntMap<Object> indexMap) {
238 var candidateTypeSymbol = inferredType.candidateType();
239 var candidateRelation = types.get(candidateTypeSymbol);
240 if (candidateRelation instanceof EnumDeclaration) {
241 // Type assertions for enum literals are added implicitly.
242 return;
243 }
244 Node node = nodes.get(nodeId);
245 if (node == null) {
246 String typeName = candidateRelation.getName();
247 if (typeName == null || typeName.isEmpty()) {
248 typeName = "node";
249 } else {
250 typeName = typeName.substring(0, 1).toLowerCase(Locale.ROOT) + typeName.substring(1);
251 }
252 int index = indexMap.getIfAbsent(typeName, 0);
253 String nodeName;
254 do {
255 index++;
256 nodeName = typeName + index;
257 } while (findNode(nodeName) != null);
258 node = ProblemFactory.eINSTANCE.createNode();
259 node.setName(nodeName);
260 problem.getNodes().add(node);
261 nodes.put(nodeId, node);
262 }
263 addAssertion(candidateRelation, LogicValue.TRUE, node);
264 var typeAnalysisResult = trace.getMetamodel().typeHierarchy().getPreservedTypes().get(candidateTypeSymbol);
265 for (var subtype : typeAnalysisResult.getDirectSubtypes()) {
266 var subtypeRelation = types.get(subtype);
267 addAssertion(subtypeRelation, LogicValue.FALSE, node);
268 }
269 }
270
271 private void addReferenceAssertions() {
272 var metamodel = trace.getMetamodel();
273 for (var partialRelation : metamodel.containmentHierarchy().keySet()) {
274 // No need to add a default value, because in a concrete model, each contained node has only a single
275 // container.
276 addAssertions(partialRelation);
277 }
278 for (var partialRelation : metamodel.directedCrossReferences().keySet()) {
279 addDefaultAssertion(partialRelation);
280 addAssertions(partialRelation);
281 }
282 // No need to add directed opposite references, because their default value is {@code unknown} and their
283 // actual value will always be computed from the value of the directed forward reference.
284 // However, undirected cross-references have to be serialized in both directions due to the default value of
285 // {@code false}.
286 for (var partialRelation : metamodel.undirectedCrossReferences().keySet()) {
287 addDefaultAssertion(partialRelation);
288 addAssertions(partialRelation);
289 }
290 }
291
292 private void addAssertions(PartialRelation partialRelation) {
293 var relation = findPartialRelation(partialRelation);
294 var cursor = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, partialRelation).getAll();
295 // Make sure to output assertions in a deterministic order.
296 var sortedTuples = new TreeSet<Tuple>();
297 while (cursor.move()) {
298 var tuple = cursor.getKey();
299 var from = nodes.get(tuple.get(0));
300 var to = nodes.get(tuple.get(1));
301 if (from == null || to == null) {
302 // One of the endpoints does not exist in the candidate model.
303 continue;
304 }
305 var value = cursor.getValue();
306 if (!value.isConcrete()) {
307 throw new IllegalStateException("Invalid %s %s for tuple %s".formatted(partialRelation, value, tuple));
308 }
309 if (value.may()) {
310 sortedTuples.add(tuple);
311 }
312 }
313 for (var tuple : sortedTuples) {
314 var from = nodes.get(tuple.get(0));
315 var to = nodes.get(tuple.get(1));
316 addAssertion(relation, LogicValue.TRUE, from, to);
317 }
318 }
319
320 private void addDefaultAssertion(PartialRelation partialRelation) {
321 var relation = findPartialRelation(partialRelation);
322 var assertion = ProblemFactory.eINSTANCE.createAssertion();
323 assertion.setDefault(true);
324 assertion.setRelation(relation);
325 int arity = ProblemUtil.getArity(relation);
326 for (int i = 0; i < arity; i++) {
327 var argument = ProblemFactory.eINSTANCE.createWildcardAssertionArgument();
328 assertion.getArguments().add(argument);
329 }
330 var logicConstant = ProblemFactory.eINSTANCE.createLogicConstant();
331 logicConstant.setLogicValue(LogicValue.FALSE);
332 assertion.setValue(logicConstant);
333 problem.getStatements().add(assertion);
334 }
335}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/DecisionTree.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/DecisionTree.java
index c732f784..5d25f148 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/DecisionTree.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/DecisionTree.java
@@ -40,8 +40,8 @@ class DecisionTree implements MutableSeed<TruthValue> {
40 } 40 }
41 41
42 @Override 42 @Override
43 public TruthValue reducedValue() { 43 public TruthValue majorityValue() {
44 return root.getOtherwiseReducedValue().getTruthValue(); 44 return root.getMajorityValue().getTruthValueOrElse(TruthValue.FALSE);
45 } 45 }
46 46
47 @Override 47 @Override
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/DecisionTreeNode.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/DecisionTreeNode.java
index ebca2634..31d6fc78 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/DecisionTreeNode.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/DecisionTreeNode.java
@@ -100,6 +100,8 @@ abstract class DecisionTreeNode {
100 100
101 protected abstract LazyIntIterable getChildKeys(); 101 protected abstract LazyIntIterable getChildKeys();
102 102
103 public abstract DecisionTreeValue getMajorityValue();
104
103 protected abstract boolean moveNextSparse(int level, DecisionTreeCursor cursor, int startIndex, 105 protected abstract boolean moveNextSparse(int level, DecisionTreeCursor cursor, int startIndex,
104 int[] sortedChildren); 106 int[] sortedChildren);
105 107
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/IntermediateNode.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/IntermediateNode.java
index 0e2f5d18..2ad216ce 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/IntermediateNode.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/IntermediateNode.java
@@ -130,6 +130,11 @@ final class IntermediateNode extends DecisionTreeNode {
130 return children.keysView(); 130 return children.keysView();
131 } 131 }
132 132
133 @Override
134 public DecisionTreeValue getMajorityValue() {
135 return otherwise.getMajorityValue();
136 }
137
133 protected boolean moveNextSparse(int level, DecisionTreeCursor cursor, int startIndex, int[] sortedChildren) { 138 protected boolean moveNextSparse(int level, DecisionTreeCursor cursor, int startIndex, int[] sortedChildren) {
134 for (int i = startIndex; i < sortedChildren.length; i++) { 139 for (int i = startIndex; i < sortedChildren.length; i++) {
135 var key = sortedChildren[i]; 140 var key = sortedChildren[i];
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/NullaryMutableSeed.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/NullaryMutableSeed.java
index 7a72c656..f100c5a6 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/NullaryMutableSeed.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/NullaryMutableSeed.java
@@ -29,14 +29,14 @@ class NullaryMutableSeed implements MutableSeed<TruthValue> {
29 } 29 }
30 30
31 @Override 31 @Override
32 public TruthValue reducedValue() { 32 public TruthValue majorityValue() {
33 return value.getTruthValue(); 33 return value.getTruthValue();
34 } 34 }
35 35
36 @Override 36 @Override
37 public TruthValue get(Tuple key) { 37 public TruthValue get(Tuple key) {
38 validateKey(key); 38 validateKey(key);
39 return reducedValue(); 39 return majorityValue();
40 } 40 }
41 41
42 private static void validateKey(Tuple key) { 42 private static void validateKey(Tuple key) {
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/TerminalNode.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/TerminalNode.java
index d3dd757c..dc501479 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/TerminalNode.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/TerminalNode.java
@@ -115,7 +115,7 @@ class TerminalNode extends DecisionTreeNode {
115 115
116 @Override 116 @Override
117 protected DecisionTreeValue getOtherwiseReducedValue() { 117 protected DecisionTreeValue getOtherwiseReducedValue() {
118 return otherwise; 118 return getMajorityValue();
119 } 119 }
120 120
121 @Override 121 @Override
@@ -124,6 +124,11 @@ class TerminalNode extends DecisionTreeNode {
124 } 124 }
125 125
126 @Override 126 @Override
127 public DecisionTreeValue getMajorityValue() {
128 return otherwise;
129 }
130
131 @Override
127 protected boolean moveNextSparse(int level, DecisionTreeCursor cursor, int startIndex, int[] sortedChildren) { 132 protected boolean moveNextSparse(int level, DecisionTreeCursor cursor, int startIndex, int[] sortedChildren) {
128 if (startIndex >= sortedChildren.length) { 133 if (startIndex >= sortedChildren.length) {
129 return false; 134 return false;
diff --git a/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/SolutionSerializerTest.java b/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/SolutionSerializerTest.java
new file mode 100644
index 00000000..2d759c86
--- /dev/null
+++ b/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/SolutionSerializerTest.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.language.semantics;
7
8import com.google.inject.Inject;
9import org.eclipse.xtext.testing.InjectWith;
10import org.eclipse.xtext.testing.extensions.InjectionExtension;
11import org.junit.jupiter.api.extension.ExtendWith;
12import org.junit.jupiter.params.ParameterizedTest;
13import org.junit.jupiter.params.provider.Arguments;
14import org.junit.jupiter.params.provider.MethodSource;
15import tools.refinery.generator.ModelGeneratorFactory;
16import tools.refinery.generator.ProblemLoader;
17import tools.refinery.language.tests.ProblemInjectorProvider;
18
19import java.io.ByteArrayOutputStream;
20import java.io.IOException;
21import java.util.Map;
22import java.util.stream.Stream;
23
24import static org.hamcrest.MatcherAssert.assertThat;
25import static org.hamcrest.Matchers.is;
26
27@ExtendWith(InjectionExtension.class)
28@InjectWith(ProblemInjectorProvider.class)
29class SolutionSerializerTest {
30 @Inject
31 ProblemLoader loader;
32
33 @Inject
34 ModelGeneratorFactory generatorFactory;
35
36 @Inject
37 SolutionSerializer serializer;
38
39 @ParameterizedTest
40 @MethodSource
41 void solutionSerializerTest(String prefix, String input, String expectedOutput) throws IOException {
42 var problem = loader.loadString(prefix + "\n" + input);
43 var generator = generatorFactory.createGenerator(problem);
44 generator.generate();
45 var solution = serializer.serializeSolution(generator.getProblemTrace(), generator.getModel());
46 String actualOutput;
47 try (var outputStream = new ByteArrayOutputStream()) {
48 solution.eResource().save(outputStream, Map.of());
49 actualOutput = outputStream.toString();
50 }
51 assertThat(actualOutput, is(prefix + "\n" + expectedOutput));
52 }
53
54 static Stream<Arguments> solutionSerializerTest() {
55 return Stream.of(Arguments.of("""
56 class Foo.
57 """, """
58 scope Foo = 3.
59 """, """
60 !exists(Foo::new).
61 Foo(foo1).
62 Foo(foo2).
63 Foo(foo3).
64 """), Arguments.of("""
65 class Foo {
66 contains Bar[2] bars
67 }
68
69 class Bar.
70 """, """
71 scope Foo = 1.
72 """, """
73 !exists(Foo::new).
74 !exists(Bar::new).
75 Foo(foo1).
76 Bar(bar1).
77 Bar(bar2).
78 bars(foo1, bar1).
79 bars(foo1, bar2).
80 """), Arguments.of("""
81 class Foo {
82 Bar[2] bars opposite foo
83 }
84
85 class Bar {
86 Foo[1] foo opposite bars
87 }
88 """, """
89 scope Foo = 1, Bar = 2.
90 """, """
91 !exists(Foo::new).
92 !exists(Bar::new).
93 Foo(foo1).
94 Bar(bar1).
95 Bar(bar2).
96 default !bars(*, *).
97 bars(foo1, bar1).
98 bars(foo1, bar2).
99 """), Arguments.of("""
100 class Person {
101 Person[2] friend opposite friend
102 }
103 """, """
104 friend(a, b).
105 friend(a, c).
106 friend(b, c).
107
108 scope Person += 0.
109 """, """
110 !exists(Person::new).
111 Person(a).
112 Person(b).
113 Person(c).
114 default !friend(*, *).
115 friend(a, b).
116 friend(a, c).
117 friend(b, a).
118 friend(b, c).
119 friend(c, a).
120 friend(c, b).
121 """), Arguments.of("""
122 class Foo {
123 Bar bar
124 }
125
126 enum Bar {
127 BAR_A,
128 BAR_B
129 }
130 """, """
131 bar(foo, BAR_A).
132
133 scope Foo += 0.
134 """, """
135 !exists(Foo::new).
136 Foo(foo).
137 default !bar(*, *).
138 bar(foo, BAR_A).
139 """), Arguments.of("""
140 class Foo.
141 class Bar extends Foo.
142 """, """
143 scope Foo = 1, Bar = 0.
144 """, """
145 !exists(Foo::new).
146 !exists(Bar::new).
147 Foo(foo1).
148 !Bar(foo1).
149 """), Arguments.of("""
150 class Foo {
151 Foo[] ref
152 }
153 """, """
154 ref(a, b).
155 !exists(b).
156
157 scope Foo += 0.
158 """, """
159 !exists(Foo::new).
160 Foo(a).
161 default !ref(*, *).
162 """));
163 }
164}
diff --git a/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/internal/DecisionTreeTests.java b/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/internal/DecisionTreeTests.java
index 2320de2c..61ce850f 100644
--- a/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/internal/DecisionTreeTests.java
+++ b/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/internal/DecisionTreeTests.java
@@ -276,6 +276,16 @@ class DecisionTreeTests {
276 assertThat(sut.getReducedValue(), is(TruthValue.TRUE)); 276 assertThat(sut.getReducedValue(), is(TruthValue.TRUE));
277 } 277 }
278 278
279 @Test
280 void overwriteWildcardAllTest() {
281 var first = new DecisionTree(2, TruthValue.UNKNOWN);
282 first.mergeValue(Tuple.of(-1, -1), TruthValue.FALSE);
283 var second = new DecisionTree(2, null);
284 second.mergeValue(Tuple.of(1, -1), TruthValue.TRUE);
285 first.overwriteValues(second);
286 assertThat(first.majorityValue(), is(TruthValue.FALSE));
287 }
288
279 private Map<Tuple, TruthValue> iterateAll(DecisionTree sut, TruthValue defaultValue, int nodeCount) { 289 private Map<Tuple, TruthValue> iterateAll(DecisionTree sut, TruthValue defaultValue, int nodeCount) {
280 var cursor = sut.getCursor(defaultValue, nodeCount); 290 var cursor = sut.getCursor(defaultValue, nodeCount);
281 var map = new LinkedHashMap<Tuple, TruthValue>(); 291 var map = new LinkedHashMap<Tuple, TruthValue>();
diff --git a/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java b/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java
index 0a5cb3c2..00dd3de3 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java
@@ -20,6 +20,7 @@ import org.eclipse.xtext.scoping.IGlobalScopeProvider;
20import org.eclipse.xtext.scoping.IScopeProvider; 20import org.eclipse.xtext.scoping.IScopeProvider;
21import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider; 21import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider;
22import org.eclipse.xtext.serializer.sequencer.ISemanticSequencer; 22import org.eclipse.xtext.serializer.sequencer.ISemanticSequencer;
23import org.eclipse.xtext.serializer.tokens.ICrossReferenceSerializer;
23import org.eclipse.xtext.validation.IDiagnosticConverter; 24import org.eclipse.xtext.validation.IDiagnosticConverter;
24import org.eclipse.xtext.validation.IResourceValidator; 25import org.eclipse.xtext.validation.IResourceValidator;
25import org.eclipse.xtext.xbase.annotations.validation.DerivedStateAwareResourceValidator; 26import org.eclipse.xtext.xbase.annotations.validation.DerivedStateAwareResourceValidator;
@@ -34,12 +35,15 @@ import tools.refinery.language.resource.ProblemResourceDescriptionStrategy;
34import tools.refinery.language.scoping.ProblemGlobalScopeProvider; 35import tools.refinery.language.scoping.ProblemGlobalScopeProvider;
35import tools.refinery.language.scoping.ProblemLocalScopeProvider; 36import tools.refinery.language.scoping.ProblemLocalScopeProvider;
36import tools.refinery.language.serializer.PreferShortAssertionsProblemSemanticSequencer; 37import tools.refinery.language.serializer.PreferShortAssertionsProblemSemanticSequencer;
38import tools.refinery.language.serializer.ProblemCrossReferenceSerializer;
37import tools.refinery.language.validation.ProblemDiagnosticConverter; 39import tools.refinery.language.validation.ProblemDiagnosticConverter;
38 40
39/** 41/**
40 * Use this class to register components to be used at runtime / without the 42 * Use this class to register components to be used at runtime / without the
41 * Equinox extension registry. 43 * Equinox extension registry.
42 */ 44 */
45// Unused methods in this class are called by reflection to configure the Xtext Injector.
46@SuppressWarnings("unused")
43public class ProblemRuntimeModule extends AbstractProblemRuntimeModule { 47public class ProblemRuntimeModule extends AbstractProblemRuntimeModule {
44 @Override 48 @Override
45 public Class<? extends IParser> bindIParser() { 49 public Class<? extends IParser> bindIParser() {
@@ -104,6 +108,10 @@ public class ProblemRuntimeModule extends AbstractProblemRuntimeModule {
104 return PreferShortAssertionsProblemSemanticSequencer.class; 108 return PreferShortAssertionsProblemSemanticSequencer.class;
105 } 109 }
106 110
111 public Class<? extends ICrossReferenceSerializer> bindICrossReferenceSerializer() {
112 return ProblemCrossReferenceSerializer.class;
113 }
114
107 public Class<? extends IDiagnosticConverter> bindIDiagnosticConverter() { 115 public Class<? extends IDiagnosticConverter> bindIDiagnosticConverter() {
108 return ProblemDiagnosticConverter.class; 116 return ProblemDiagnosticConverter.class;
109 } 117 }
diff --git a/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java b/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java
index 55a5ac20..0f3bd3ee 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java
@@ -69,12 +69,24 @@ public class ProblemFormatter extends AbstractJavaFormatter {
69 doc.prepend(region.keyword("{"), this::oneSpace); 69 doc.prepend(region.keyword("{"), this::oneSpace);
70 doc.append(region.keyword("{"), it -> it.setNewLines(1, 1, 2)); 70 doc.append(region.keyword("{"), it -> it.setNewLines(1, 1, 2));
71 doc.prepend(region.keyword("}"), it -> it.setNewLines(1, 1, 2)); 71 doc.prepend(region.keyword("}"), it -> it.setNewLines(1, 1, 2));
72 doc.interior(region.keyword("{"), region.keyword("}"), IHiddenRegionFormatter::indent);
72 doc.prepend(region.keyword("."), this::noSpace); 73 doc.prepend(region.keyword("."), this::noSpace);
73 for (var featureDeclaration : classDeclaration.getFeatureDeclarations()) { 74 for (var featureDeclaration : classDeclaration.getFeatureDeclarations()) {
74 doc.format(featureDeclaration); 75 doc.format(featureDeclaration);
75 } 76 }
76 } 77 }
77 78
79 protected void format(EnumDeclaration enumDeclaration, IFormattableDocument doc) {
80 surroundNewLines(doc, enumDeclaration, this::twoNewLines);
81 var region = regionFor(enumDeclaration);
82 doc.append(region.keyword("enum"), this::oneSpace);
83 doc.prepend(region.keyword("{"), this::oneSpace);
84 doc.append(region.keyword("{"), it -> it.setNewLines(1, 1, 2));
85 doc.prepend(region.keyword("}"), it -> it.setNewLines(1, 1, 2));
86 doc.interior(region.keyword("{"), region.keyword("}"), IHiddenRegionFormatter::indent);
87 doc.prepend(region.keyword("."), this::noSpace);
88 }
89
78 protected void format(PredicateDefinition predicateDefinition, IFormattableDocument doc) { 90 protected void format(PredicateDefinition predicateDefinition, IFormattableDocument doc) {
79 surroundNewLines(doc, predicateDefinition, this::twoNewLines); 91 surroundNewLines(doc, predicateDefinition, this::twoNewLines);
80 var region = regionFor(predicateDefinition); 92 var region = regionFor(predicateDefinition);
@@ -151,14 +163,14 @@ public class ProblemFormatter extends AbstractJavaFormatter {
151 } 163 }
152 164
153 protected void surroundNewLines(IFormattableDocument doc, EObject eObject, 165 protected void surroundNewLines(IFormattableDocument doc, EObject eObject,
154 Procedure1<? super IHiddenRegionFormatter> init) { 166 Procedure1<? super IHiddenRegionFormatter> init) {
155 var region = doc.getRequest().getTextRegionAccess().regionForEObject(eObject); 167 var region = doc.getRequest().getTextRegionAccess().regionForEObject(eObject);
156 preprendNewLines(doc, region, init); 168 preprendNewLines(doc, region, init);
157 appendNewLines(doc, region, init); 169 appendNewLines(doc, region, init);
158 } 170 }
159 171
160 protected void preprendNewLines(IFormattableDocument doc, ISequentialRegion region, 172 protected void preprendNewLines(IFormattableDocument doc, ISequentialRegion region,
161 Procedure1<? super IHiddenRegionFormatter> init) { 173 Procedure1<? super IHiddenRegionFormatter> init) {
162 if (region == null) { 174 if (region == null) {
163 return; 175 return;
164 } 176 }
@@ -174,7 +186,7 @@ public class ProblemFormatter extends AbstractJavaFormatter {
174 } 186 }
175 187
176 protected void appendNewLines(IFormattableDocument doc, ISequentialRegion region, 188 protected void appendNewLines(IFormattableDocument doc, ISequentialRegion region,
177 Procedure1<? super IHiddenRegionFormatter> init) { 189 Procedure1<? super IHiddenRegionFormatter> init) {
178 if (region == null) { 190 if (region == null) {
179 return; 191 return;
180 } 192 }
diff --git a/subprojects/language/src/main/java/tools/refinery/language/serializer/PreferShortAssertionsProblemSemanticSequencer.java b/subprojects/language/src/main/java/tools/refinery/language/serializer/PreferShortAssertionsProblemSemanticSequencer.java
index b9cafbc2..3432b2d8 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/serializer/PreferShortAssertionsProblemSemanticSequencer.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/serializer/PreferShortAssertionsProblemSemanticSequencer.java
@@ -21,8 +21,7 @@ public class PreferShortAssertionsProblemSemanticSequencer extends ProblemSemant
21 21
22 @Override 22 @Override
23 protected void sequence_Assertion(ISerializationContext context, Assertion semanticObject) { 23 protected void sequence_Assertion(ISerializationContext context, Assertion semanticObject) {
24 if (semanticObject.isDefault() || 24 if (!(semanticObject.getValue() instanceof LogicConstant logicConstant) ||
25 !(semanticObject.getValue() instanceof LogicConstant logicConstant) ||
26 logicConstant.getLogicValue() == LogicValue.ERROR) { 25 logicConstant.getLogicValue() == LogicValue.ERROR) {
27 super.sequence_Assertion(context, semanticObject); 26 super.sequence_Assertion(context, semanticObject);
28 return; 27 return;
@@ -39,6 +38,9 @@ public class PreferShortAssertionsProblemSemanticSequencer extends ProblemSemant
39 } 38 }
40 var feeder = createSequencerFeeder(context, semanticObject); 39 var feeder = createSequencerFeeder(context, semanticObject);
41 var access = grammarAccess.getAssertionAccess(); 40 var access = grammarAccess.getAssertionAccess();
41 if (semanticObject.isDefault()) {
42 feeder.accept(access.getDefaultDefaultKeyword_0_0());
43 }
42 feeder.accept(access.getValueShortLogicConstantParserRuleCall_1_1_0_0(), logicConstant); 44 feeder.accept(access.getValueShortLogicConstantParserRuleCall_1_1_0_0(), logicConstant);
43 feeder.accept(access.getRelationRelationQualifiedNameParserRuleCall_1_1_1_0_1(), semanticObject.getRelation()); 45 feeder.accept(access.getRelationRelationQualifiedNameParserRuleCall_1_1_1_0_1(), semanticObject.getRelation());
44 var iterator = semanticObject.getArguments().iterator(); 46 var iterator = semanticObject.getArguments().iterator();
diff --git a/subprojects/language/src/main/java/tools/refinery/language/serializer/ProblemCrossReferenceSerializer.java b/subprojects/language/src/main/java/tools/refinery/language/serializer/ProblemCrossReferenceSerializer.java
new file mode 100644
index 00000000..42950e61
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/serializer/ProblemCrossReferenceSerializer.java
@@ -0,0 +1,167 @@
1/*******************************************************************************
2 * Copyright (c) 2008, 2018 itemis AG (http://www.itemis.eu) and others.
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/>
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-2.0.
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.language.serializer;
10
11import com.google.common.collect.Lists;
12import com.google.inject.Inject;
13import org.eclipse.emf.common.util.URI;
14import org.eclipse.emf.ecore.EObject;
15import org.eclipse.emf.ecore.EReference;
16import org.eclipse.xtext.CrossReference;
17import org.eclipse.xtext.EcoreUtil2;
18import org.eclipse.xtext.GrammarUtil;
19import org.eclipse.xtext.IGrammarAccess;
20import org.eclipse.xtext.conversion.IValueConverterService;
21import org.eclipse.xtext.conversion.ValueConverterException;
22import org.eclipse.xtext.linking.impl.LinkingHelper;
23import org.eclipse.xtext.naming.IQualifiedNameConverter;
24import org.eclipse.xtext.naming.QualifiedName;
25import org.eclipse.xtext.nodemodel.INode;
26import org.eclipse.xtext.scoping.IScope;
27import org.eclipse.xtext.scoping.IScopeProvider;
28import org.eclipse.xtext.serializer.diagnostic.ISerializationDiagnostic;
29import org.eclipse.xtext.serializer.diagnostic.SerializationDiagnostic;
30import org.eclipse.xtext.serializer.tokens.CrossReferenceSerializer;
31import org.eclipse.xtext.serializer.tokens.SerializerScopeProviderBinding;
32import org.eclipse.xtext.util.EmfFormatter;
33
34import java.util.List;
35
36public class ProblemCrossReferenceSerializer extends CrossReferenceSerializer {
37 public static final String AMBIGUOUS_REFERENCE_DIAGNOSTIC = "tools.refinery.language.serializer" +
38 ".ProblemCrossReferenceSerializer.AMBIGUOUS_REFERENCE";
39
40 @Inject
41 private LinkingHelper linkingHelper;
42
43 @Inject
44 private IQualifiedNameConverter qualifiedNameConverter;
45
46 @Inject
47 @SerializerScopeProviderBinding
48 private IScopeProvider scopeProvider;
49
50 @Inject
51 private IValueConverterService valueConverter;
52
53 @Inject
54 private IGrammarAccess grammarAccess;
55
56 @Override
57 public String serializeCrossRef(EObject semanticObject, CrossReference crossref, EObject target, INode node,
58 ISerializationDiagnostic.Acceptor errors) {
59 if ((target == null || target.eIsProxy()) && node != null) {
60 return tokenUtil.serializeNode(node);
61 }
62
63 final EReference ref = GrammarUtil.getReference(crossref, semanticObject.eClass());
64 final IScope scope = scopeProvider.getScope(semanticObject, ref);
65 if (scope == null) {
66 if (errors != null) {
67 errors.accept(diagnostics.getNoScopeFoundDiagnostic(semanticObject, crossref, target));
68 }
69 return null;
70 }
71
72 if (target != null && target.eIsProxy()) {
73 target = handleProxy(target, semanticObject, ref);
74 }
75
76 if (target != null && node != null) {
77 String text = linkingHelper.getCrossRefNodeAsString(node, true);
78 QualifiedName qualifiedName = qualifiedNameConverter.toQualifiedName(text);
79 URI targetUri = EcoreUtil2.getPlatformResourceOrNormalizedURI(target);
80 if (isUniqueInScope(scope, qualifiedName, targetUri)) {
81 return tokenUtil.serializeNode(node);
82 }
83 }
84
85 return getCrossReferenceNameFromScope(semanticObject, crossref, target, scope, errors);
86 }
87
88 private boolean isUniqueInScope(IScope scope, QualifiedName qualifiedName, URI targetUri) {
89 var iterator = scope.getElements(qualifiedName).iterator();
90 if (!iterator.hasNext()) {
91 return false;
92 }
93 var description = iterator.next();
94 return targetUri.equals(description.getEObjectURI()) && !iterator.hasNext();
95 }
96
97 @Override
98 protected String getCrossReferenceNameFromScope(EObject semanticObject, CrossReference crossref, EObject target,
99 IScope scope, ISerializationDiagnostic.Acceptor errors) {
100 var ruleName = linkingHelper.getRuleNameFrom(crossref);
101 var targetUri = EcoreUtil2.getPlatformResourceOrNormalizedURI(target);
102 FoundName foundName = FoundName.NONE;
103 int shortestNameLength = Integer.MAX_VALUE;
104 String shortestName = null;
105 List<ISerializationDiagnostic> recordedErrors = null;
106 for (var description : scope.getElements(target)) {
107 var qualifiedName = description.getName();
108 var segmentCount = qualifiedName.getSegmentCount();
109 if (shortestName != null && segmentCount >= shortestNameLength) {
110 continue;
111 }
112 if (isUniqueInScope(scope, qualifiedName, targetUri)) {
113 var unconverted = qualifiedNameConverter.toString(qualifiedName);
114 try {
115 shortestName = valueConverter.toString(unconverted, ruleName);
116 shortestNameLength = segmentCount;
117 foundName = FoundName.VALID;
118 } catch (ValueConverterException e) {
119 if (recordedErrors == null) {
120 recordedErrors = Lists.newArrayList();
121 }
122 recordedErrors.add(diagnostics.getValueConversionExceptionDiagnostic(semanticObject, crossref,
123 unconverted, e));
124 }
125 } else if (foundName == FoundName.NONE) {
126 foundName = FoundName.AMBIGUOUS;
127 }
128 }
129 handleErrors(semanticObject, crossref, target, scope, errors, recordedErrors, foundName);
130 return shortestName;
131 }
132
133 private void handleErrors(
134 EObject semanticObject, CrossReference crossref, EObject target, IScope scope,
135 ISerializationDiagnostic.Acceptor errors, List<ISerializationDiagnostic> recordedErrors,
136 FoundName foundName) {
137 if (errors == null) {
138 return;
139 }
140 if (recordedErrors != null) {
141 recordedErrors.forEach(errors::accept);
142 }
143 if (foundName == FoundName.NONE) {
144 errors.accept(diagnostics.getNoEObjectDescriptionFoundDiagnostic(semanticObject, crossref, target,
145 scope));
146 } else if (foundName == FoundName.AMBIGUOUS) {
147 // Computation of reference name copied from
148 // {@link org.eclipse.xtext.serializer.diagnostic.TokenDiagnosticProvider#getFullReferenceName}.
149 var ref = GrammarUtil.getReference(crossref);
150 var clazz = semanticObject.eClass().getName();
151 if (ref.getEContainingClass() != semanticObject.eClass()) {
152 clazz = ref.getEContainingClass().getName() + "(" + clazz + ")";
153 }
154 var message = "No unambiguous name could be found in scope %s.%s for %s"
155 .formatted(clazz, ref.getName(), EmfFormatter.objPath(target));
156 var diagnostic = new SerializationDiagnostic(AMBIGUOUS_REFERENCE_DIAGNOSTIC, semanticObject, crossref,
157 grammarAccess.getGrammar(), message);
158 errors.accept(diagnostic);
159 }
160 }
161
162 private enum FoundName {
163 NONE,
164 AMBIGUOUS,
165 VALID
166 }
167}
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java
index 9f4f2bbf..65675b6b 100644
--- a/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java
+++ b/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java
@@ -74,8 +74,8 @@ class ProblemSerializerTest {
74 } 74 }
75 75
76 @ParameterizedTest 76 @ParameterizedTest
77 @MethodSource 77 @MethodSource("assertionTest")
78 void defaultAssertionTest(LogicValue value, String valueAsString) { 78 void defaultAssertionTest(LogicValue value, String serializedAssertion) {
79 var pred = createPred(); 79 var pred = createPred();
80 var node = ProblemFactory.eINSTANCE.createNode(); 80 var node = ProblemFactory.eINSTANCE.createNode();
81 node.setName("a"); 81 node.setName("a");
@@ -88,12 +88,7 @@ class ProblemSerializerTest {
88 pred foo(node p). 88 pred foo(node p).
89 89
90 indiv a. 90 indiv a.
91 default foo(a):\040""" + valueAsString + ".\n"); 91 default\040""" + serializedAssertion + "\n");
92 }
93
94 static Stream<Arguments> defaultAssertionTest() {
95 return Stream.of(Arguments.of(LogicValue.TRUE, "true"), Arguments.of(LogicValue.FALSE, "false"),
96 Arguments.of(LogicValue.UNKNOWN, "unknown"), Arguments.of(LogicValue.ERROR, "error"));
97 } 92 }
98 93
99 @Test 94 @Test
@@ -161,6 +156,7 @@ class ProblemSerializerTest {
161 problem.getStatements().add(assertion); 156 problem.getStatements().add(assertion);
162 } 157 }
163 158
159
164 @Test 160 @Test
165 void implicitVariableTest() { 161 void implicitVariableTest() {
166 var pred = ProblemFactory.eINSTANCE.createPredicateDefinition(); 162 var pred = ProblemFactory.eINSTANCE.createPredicateDefinition();
@@ -232,20 +228,77 @@ class ProblemSerializerTest {
232 """); 228 """);
233 } 229 }
234 230
231 @Test
232 void unambiguousNameTest() {
233 createClassAndAssertion("Foo", "foo");
234
235 assertSerializedResult("""
236 class Foo {
237 Foo ref
238 }
239
240 ref(foo, foo).
241 """);
242 }
243
244 @Test
245 void ambiguousNameTest() {
246 createClassAndAssertion("Foo", "foo");
247 createClassAndAssertion("Bar", "bar");
248
249 assertSerializedResult("""
250 class Foo {
251 Foo ref
252 }
253
254 Foo::ref(foo, foo).
255
256 class Bar {
257 Bar ref
258 }
259
260 Bar::ref(bar, bar).
261 """);
262 }
263
264 private void createClassAndAssertion(String className, String nodeName) {
265 var classDeclaration = ProblemFactory.eINSTANCE.createClassDeclaration();
266 classDeclaration.setName(className);
267 var referenceDeclaration = ProblemFactory.eINSTANCE.createReferenceDeclaration();
268 referenceDeclaration.setReferenceType(classDeclaration);
269 referenceDeclaration.setName("ref");
270 classDeclaration.getFeatureDeclarations().add(referenceDeclaration);
271 problem.getStatements().add(classDeclaration);
272 var node = ProblemFactory.eINSTANCE.createNode();
273 node.setName(nodeName);
274 problem.getNodes().add(node);
275 createBinaryAssertion(referenceDeclaration, node, node);
276 }
277
278 private void createBinaryAssertion(Relation relation, Node from, Node to) {
279 var assertion = ProblemFactory.eINSTANCE.createAssertion();
280 assertion.setRelation(relation);
281 var fromArgument = ProblemFactory.eINSTANCE.createNodeAssertionArgument();
282 fromArgument.setNode(from);
283 assertion.getArguments().add(fromArgument);
284 var toArgument = ProblemFactory.eINSTANCE.createNodeAssertionArgument();
285 toArgument.setNode(to);
286 assertion.getArguments().add(toArgument);
287 var value = ProblemFactory.eINSTANCE.createLogicConstant();
288 value.setLogicValue(LogicValue.TRUE);
289 assertion.setValue(value);
290 problem.getStatements().add(assertion);
291 }
292
235 private void assertSerializedResult(String expected) { 293 private void assertSerializedResult(String expected) {
236 var outputStream = new ByteArrayOutputStream(); 294 String problemString;
237 try { 295 try (var outputStream = new ByteArrayOutputStream()) {
238 resource.save(outputStream, Map.of()); 296 resource.save(outputStream, Map.of());
297 problemString = outputStream.toString();
239 } catch (IOException e) { 298 } catch (IOException e) {
240 throw new AssertionError("Failed to serialize problem", e); 299 throw new AssertionError("Failed to serialize problem", e);
241 } finally {
242 try {
243 outputStream.close();
244 } catch (IOException e) {
245 // Nothing to handle in a test.
246 }
247 } 300 }
248 var problemString = outputStream.toString(); 301 // Nothing to handle in a test.
249 302
250 assertThat(problemString.replace("\r\n", "\n"), equalTo(expected.replace("\r\n", "\n"))); 303 assertThat(problemString.replace("\r\n", "\n"), equalTo(expected.replace("\r\n", "\n")));
251 } 304 }
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
index ce3efb21..22f38ca9 100644
--- 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
@@ -10,10 +10,10 @@ import tools.refinery.store.model.Model;
10import java.util.Random; 10import java.util.Random;
11 11
12public class BestFirstExplorer extends BestFirstWorker { 12public class BestFirstExplorer extends BestFirstWorker {
13 final int id; 13 final long id;
14 Random random; 14 Random random;
15 15
16 public BestFirstExplorer(BestFirstStoreManager storeManager, Model model, int id) { 16 public BestFirstExplorer(BestFirstStoreManager storeManager, Model model, long id) {
17 super(storeManager, model); 17 super(storeManager, model);
18 this.id = id; 18 this.id = id;
19 // The use of a non-cryptographic random generator is safe here, because we only use it to direct the state 19 // The use of a non-cryptographic random generator is safe here, because we only use it to direct the state
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
index 3d32f84c..c20ab9a0 100644
--- 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
@@ -79,7 +79,7 @@ public class BestFirstStoreManager {
79 startExploration(initial, 1); 79 startExploration(initial, 1);
80 } 80 }
81 81
82 public void startExploration(Version initial, int randomSeed) { 82 public void startExploration(Version initial, long randomSeed) {
83 BestFirstExplorer bestFirstExplorer = new BestFirstExplorer(this, modelStore.createModelForState(initial), 83 BestFirstExplorer bestFirstExplorer = new BestFirstExplorer(this, modelStore.createModelForState(initial),
84 randomSeed); 84 randomSeed);
85 bestFirstExplorer.explore(); 85 bestFirstExplorer.explore();
diff --git a/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/PredicateScopeTest.java b/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/PredicateScopeTest.java
index a2c56a6b..5ee97ce1 100644
--- a/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/PredicateScopeTest.java
+++ b/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/PredicateScopeTest.java
@@ -27,7 +27,6 @@ import tools.refinery.store.reasoning.translator.containment.ContainmentHierarch
27import tools.refinery.store.reasoning.translator.metamodel.Metamodel; 27import tools.refinery.store.reasoning.translator.metamodel.Metamodel;
28import tools.refinery.store.reasoning.translator.metamodel.MetamodelTranslator; 28import tools.refinery.store.reasoning.translator.metamodel.MetamodelTranslator;
29import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator; 29import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
30import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity;
31import tools.refinery.store.representation.TruthValue; 30import tools.refinery.store.representation.TruthValue;
32import tools.refinery.store.representation.cardinality.CardinalityIntervals; 31import tools.refinery.store.representation.cardinality.CardinalityIntervals;
33import tools.refinery.store.statecoding.StateCoderAdapter; 32import tools.refinery.store.statecoding.StateCoderAdapter;
@@ -109,10 +108,16 @@ class PredicateScopeTest {
109 private ModelStore createStore() { 108 private ModelStore createStore() {
110 var metamodel = Metamodel.builder() 109 var metamodel = Metamodel.builder()
111 .type(index) 110 .type(index)
112 .reference(next, index, false, 111 .reference(next, builder -> builder
113 ConstrainedMultiplicity.of(CardinalityIntervals.LONE, nextInvalidMultiplicity), index, prev) 112 .source(index)
114 .reference(prev, index, false, 113 .target(index)
115 ConstrainedMultiplicity.of(CardinalityIntervals.LONE, prevInvalidMultiplicity), index, next) 114 .multiplicity(CardinalityIntervals.LONE, nextInvalidMultiplicity)
115 .opposite(prev))
116 .reference(prev, builder -> builder
117 .source(index)
118 .target(index)
119 .multiplicity(CardinalityIntervals.LONE, prevInvalidMultiplicity)
120 .opposite(next))
116 .build(); 121 .build();
117 return ModelStore.builder() 122 return ModelStore.builder()
118 .with(QueryInterpreterAdapter.builder()) 123 .with(QueryInterpreterAdapter.builder())
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/MapBasedSeed.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/MapBasedSeed.java
index 8b1c3bb3..3b78db02 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/MapBasedSeed.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/MapBasedSeed.java
@@ -12,19 +12,19 @@ import tools.refinery.store.tuple.Tuple;
12import java.util.Map; 12import java.util.Map;
13import java.util.Objects; 13import java.util.Objects;
14 14
15record MapBasedSeed<T>(int arity, Class<T> valueType, T reducedValue, Map<Tuple, T> map) implements Seed<T> { 15record MapBasedSeed<T>(int arity, Class<T> valueType, T majorityValue, Map<Tuple, T> map) implements Seed<T> {
16 @Override 16 @Override
17 public T get(Tuple key) { 17 public T get(Tuple key) {
18 var value = map.get(key); 18 var value = map.get(key);
19 return value == null ? reducedValue : value; 19 return value == null ? majorityValue : value;
20 } 20 }
21 21
22 @Override 22 @Override
23 public Cursor<Tuple, T> getCursor(T defaultValue, int nodeCount) { 23 public Cursor<Tuple, T> getCursor(T defaultValue, int nodeCount) {
24 if (Objects.equals(defaultValue, reducedValue)) { 24 if (Objects.equals(defaultValue, majorityValue)) {
25 return Cursors.of(map); 25 return Cursors.of(map);
26 } 26 }
27 return new CartesianProductCursor<>(arity, nodeCount, reducedValue, defaultValue, map); 27 return new CartesianProductCursor<>(arity, nodeCount, majorityValue, defaultValue, map);
28 } 28 }
29 29
30 private static class CartesianProductCursor<T> implements Cursor<Tuple, T> { 30 private static class CartesianProductCursor<T> implements Cursor<Tuple, T> {
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java
index 732efbcc..d9bad866 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java
@@ -19,7 +19,7 @@ public interface Seed<T> {
19 19
20 Class<T> valueType(); 20 Class<T> valueType();
21 21
22 T reducedValue(); 22 T majorityValue();
23 23
24 T get(Tuple key); 24 T get(Tuple key);
25 25
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInfo.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInfo.java
index d8c6a5ea..7241b032 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInfo.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInfo.java
@@ -7,9 +7,11 @@ package tools.refinery.store.reasoning.translator.crossreference;
7 7
8import tools.refinery.store.reasoning.representation.PartialRelation; 8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; 9import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
10import tools.refinery.store.representation.TruthValue;
10 11
11public record DirectedCrossReferenceInfo(PartialRelation sourceType, Multiplicity sourceMultiplicity, 12public record DirectedCrossReferenceInfo(PartialRelation sourceType, Multiplicity sourceMultiplicity,
12 PartialRelation targetType, Multiplicity targetMultiplicity) { 13 PartialRelation targetType, Multiplicity targetMultiplicity,
14 TruthValue defaultValue) {
13 public boolean isConstrained() { 15 public boolean isConstrained() {
14 return sourceMultiplicity.isConstrained() || targetMultiplicity.isConstrained(); 16 return sourceMultiplicity.isConstrained() || targetMultiplicity.isConstrained();
15 } 17 }
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInitializer.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInitializer.java
new file mode 100644
index 00000000..9347e91e
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInitializer.java
@@ -0,0 +1,52 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.crossreference;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.ReasoningAdapter;
10import tools.refinery.store.reasoning.refinement.PartialModelInitializer;
11import tools.refinery.store.reasoning.representation.PartialRelation;
12import tools.refinery.store.reasoning.seed.ModelSeed;
13import tools.refinery.store.representation.Symbol;
14import tools.refinery.store.representation.TruthValue;
15import tools.refinery.store.tuple.Tuple;
16
17class DirectedCrossReferenceInitializer implements PartialModelInitializer {
18 private final PartialRelation linkType;
19 private final PartialRelation sourceType;
20 private final PartialRelation targetType;
21 private final Symbol<TruthValue> symbol;
22
23 public DirectedCrossReferenceInitializer(PartialRelation linkType, PartialRelation sourceType,
24 PartialRelation targetType, Symbol<TruthValue> symbol) {
25 this.linkType = linkType;
26 this.sourceType = sourceType;
27 this.targetType = targetType;
28 this.symbol = symbol;
29 }
30
31 @Override
32 public void initialize(Model model, ModelSeed modelSeed) {
33 var reasoningAdapter = model.getAdapter(ReasoningAdapter.class);
34 var sourceRefiner = reasoningAdapter.getRefiner(sourceType);
35 var targetRefiner = reasoningAdapter.getRefiner(targetType);
36 var interpretation = model.getInterpretation(symbol);
37 var cursor = modelSeed.getCursor(linkType, symbol.defaultValue());
38 while (cursor.move()) {
39 var key = cursor.getKey();
40 var value = cursor.getValue();
41 interpretation.put(key, value);
42 if (value.must()) {
43 boolean merged = sourceRefiner.merge(Tuple.of(key.get(0)), TruthValue.TRUE) &&
44 targetRefiner.merge(Tuple.of(key.get(1)), TruthValue.TRUE);
45 if (!merged) {
46 throw new IllegalArgumentException("Failed to merge end types of reference %s for key %s"
47 .formatted(linkType, key));
48 }
49 }
50 }
51 }
52}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceTranslator.java
index 9028337c..fc7477f1 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceTranslator.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceTranslator.java
@@ -5,6 +5,7 @@
5 */ 5 */
6package tools.refinery.store.reasoning.translator.crossreference; 6package tools.refinery.store.reasoning.translator.crossreference;
7 7
8import tools.refinery.store.dse.propagation.PropagationBuilder;
8import tools.refinery.store.dse.transition.Rule; 9import tools.refinery.store.dse.transition.Rule;
9import tools.refinery.store.model.ModelStoreBuilder; 10import tools.refinery.store.model.ModelStoreBuilder;
10import tools.refinery.store.model.ModelStoreConfiguration; 11import tools.refinery.store.model.ModelStoreConfiguration;
@@ -14,9 +15,9 @@ import tools.refinery.store.query.view.ForbiddenView;
14import tools.refinery.store.reasoning.lifting.DnfLifter; 15import tools.refinery.store.reasoning.lifting.DnfLifter;
15import tools.refinery.store.reasoning.literal.Concreteness; 16import tools.refinery.store.reasoning.literal.Concreteness;
16import tools.refinery.store.reasoning.literal.Modality; 17import tools.refinery.store.reasoning.literal.Modality;
17import tools.refinery.store.reasoning.refinement.RefinementBasedInitializer;
18import tools.refinery.store.reasoning.representation.PartialRelation; 18import tools.refinery.store.reasoning.representation.PartialRelation;
19import tools.refinery.store.reasoning.translator.PartialRelationTranslator; 19import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
20import tools.refinery.store.reasoning.translator.TranslationException;
20import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator; 21import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator;
21import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; 22import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
22import tools.refinery.store.representation.Symbol; 23import tools.refinery.store.representation.Symbol;
@@ -24,6 +25,7 @@ import tools.refinery.store.representation.TruthValue;
24 25
25import static tools.refinery.store.query.literal.Literals.not; 26import static tools.refinery.store.query.literal.Literals.not;
26import static tools.refinery.store.reasoning.actions.PartialActionLiterals.add; 27import static tools.refinery.store.reasoning.actions.PartialActionLiterals.add;
28import static tools.refinery.store.reasoning.actions.PartialActionLiterals.merge;
27import static tools.refinery.store.reasoning.literal.PartialLiterals.*; 29import static tools.refinery.store.reasoning.literal.PartialLiterals.*;
28import static tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator.MULTI_VIEW; 30import static tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator.MULTI_VIEW;
29 31
@@ -35,7 +37,7 @@ public class DirectedCrossReferenceTranslator implements ModelStoreConfiguration
35 public DirectedCrossReferenceTranslator(PartialRelation linkType, DirectedCrossReferenceInfo info) { 37 public DirectedCrossReferenceTranslator(PartialRelation linkType, DirectedCrossReferenceInfo info) {
36 this.linkType = linkType; 38 this.linkType = linkType;
37 this.info = info; 39 this.info = info;
38 symbol = Symbol.of(linkType.name(), 2, TruthValue.class, TruthValue.UNKNOWN); 40 symbol = Symbol.of(linkType.name(), 2, TruthValue.class, info.defaultValue());
39 } 41 }
40 42
41 @Override 43 @Override
@@ -45,41 +47,77 @@ public class DirectedCrossReferenceTranslator implements ModelStoreConfiguration
45 var targetType = info.targetType(); 47 var targetType = info.targetType();
46 var mayNewSource = createMayHelper(sourceType, info.sourceMultiplicity(), false); 48 var mayNewSource = createMayHelper(sourceType, info.sourceMultiplicity(), false);
47 var mayNewTarget = createMayHelper(targetType, info.targetMultiplicity(), true); 49 var mayNewTarget = createMayHelper(targetType, info.targetMultiplicity(), true);
48 var forbiddenView = new ForbiddenView(symbol);
49 var mayName = DnfLifter.decorateName(name, Modality.MAY, Concreteness.PARTIAL); 50 var mayName = DnfLifter.decorateName(name, Modality.MAY, Concreteness.PARTIAL);
50 51
51 storeBuilder.with(PartialRelationTranslator.of(linkType) 52 var defaultValue = info.defaultValue();
52 .symbol(symbol) 53 if (defaultValue.must()) {
53 .may(Query.of(mayName, (builder, source, target) -> { 54 throw new TranslationException(linkType, "Unsupported default value %s for directed cross references %s"
55 .formatted(defaultValue, linkType));
56 }
57
58 var translator = PartialRelationTranslator.of(linkType);
59 translator.symbol(symbol);
60 if (defaultValue.may()) {
61 var forbiddenView = new ForbiddenView(symbol);
62 translator.may(Query.of(mayName, (builder, source, target) -> {
63 builder.clause(
64 mayNewSource.call(source),
65 mayNewTarget.call(target),
66 not(forbiddenView.call(source, target))
67 );
68 if (info.isConstrained()) {
69 // Violation of monotonicity:
70 // Edges violating upper multiplicity will not be marked as {@code ERROR}, but the
71 // corresponding error pattern will already mark the node as invalid.
72 builder.clause(
73 must(linkType.call(source, target)),
74 not(forbiddenView.call(source, target)),
75 may(sourceType.call(source)),
76 may(targetType.call(target))
77 );
78 }
79 }));
80 } else {
81 var propagationBuilder = storeBuilder.getAdapter(PropagationBuilder.class);
82 propagationBuilder.rule(Rule.of(name + "#invalidLink", (builder, p1, p2) -> {
83 builder.clause(
84 may(linkType.call(p1, p2)),
85 not(may(sourceType.call(p1)))
86 );
87 builder.clause(
88 may(linkType.call(p1, p2)),
89 not(may(targetType.call(p2)))
90 );
91 if (info.isConstrained()) {
92 builder.clause(
93 may(linkType.call(p1, p2)),
94 not(must(linkType.call(p1, p2))),
95 not(mayNewSource.call(p1))
96 );
54 builder.clause( 97 builder.clause(
55 mayNewSource.call(source), 98 may(linkType.call(p1, p2)),
56 mayNewTarget.call(target), 99 not(must(linkType.call(p1, p2))),
57 not(forbiddenView.call(source, target)) 100 not(mayNewTarget.call(p2))
58 ); 101 );
59 if (info.isConstrained()) { 102 }
60 // Violation of monotonicity: 103 builder.action(
61 // Edges violating upper multiplicity will not be marked as {@code ERROR}, but the 104 merge(linkType, TruthValue.FALSE, p1, p2)
62 // corresponding error pattern will already mark the node as invalid. 105 );
63 builder.clause( 106 }));
64 must(linkType.call(source, target)), 107 }
65 not(forbiddenView.call(source, target)), 108 translator.refiner(DirectedCrossReferenceRefiner.of(symbol, sourceType, targetType));
66 may(sourceType.call(source)), 109 translator.initializer(new DirectedCrossReferenceInitializer(linkType, sourceType, targetType, symbol));
67 may(targetType.call(target)) 110 translator.decision(Rule.of(linkType.name(), (builder, source, target) -> builder
68 ); 111 .clause(
69 } 112 may(linkType.call(source, target)),
70 })) 113 not(candidateMust(linkType.call(source, target))),
71 .refiner(DirectedCrossReferenceRefiner.of(symbol, sourceType, targetType)) 114 not(MULTI_VIEW.call(source)),
72 .initializer(new RefinementBasedInitializer<>(linkType)) 115 not(MULTI_VIEW.call(target))
73 .decision(Rule.of(linkType.name(), (builder, source, target) -> builder 116 )
74 .clause( 117 .action(
75 may(linkType.call(source, target)), 118 add(linkType, source, target)
76 not(candidateMust(linkType.call(source, target))), 119 )));
77 not(MULTI_VIEW.call(source)), 120 storeBuilder.with(translator);
78 not(MULTI_VIEW.call(target))
79 )
80 .action(
81 add(linkType, source, target)
82 ))));
83 121
84 storeBuilder.with(new InvalidMultiplicityErrorTranslator(sourceType, linkType, false, 122 storeBuilder.with(new InvalidMultiplicityErrorTranslator(sourceType, linkType, false,
85 info.sourceMultiplicity())); 123 info.sourceMultiplicity()));
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInfo.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInfo.java
index fe99bc54..34e9032a 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInfo.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInfo.java
@@ -7,8 +7,9 @@ package tools.refinery.store.reasoning.translator.crossreference;
7 7
8import tools.refinery.store.reasoning.representation.PartialRelation; 8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; 9import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
10import tools.refinery.store.representation.TruthValue;
10 11
11public record UndirectedCrossReferenceInfo(PartialRelation type, Multiplicity multiplicity) { 12public record UndirectedCrossReferenceInfo(PartialRelation type, Multiplicity multiplicity, TruthValue defaultValue) {
12 public boolean isConstrained() { 13 public boolean isConstrained() {
13 return multiplicity.isConstrained(); 14 return multiplicity.isConstrained();
14 } 15 }
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInitializer.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInitializer.java
new file mode 100644
index 00000000..77339aa0
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInitializer.java
@@ -0,0 +1,84 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.crossreference;
7
8import org.jetbrains.annotations.NotNull;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.reasoning.ReasoningAdapter;
11import tools.refinery.store.reasoning.refinement.PartialModelInitializer;
12import tools.refinery.store.reasoning.representation.PartialRelation;
13import tools.refinery.store.reasoning.seed.ModelSeed;
14import tools.refinery.store.reasoning.translator.TranslationException;
15import tools.refinery.store.representation.Symbol;
16import tools.refinery.store.representation.TruthValue;
17import tools.refinery.store.tuple.Tuple;
18
19import java.util.LinkedHashMap;
20
21class UndirectedCrossReferenceInitializer implements PartialModelInitializer {
22 private final PartialRelation linkType;
23 private final PartialRelation sourceType;
24 private final Symbol<TruthValue> symbol;
25
26 UndirectedCrossReferenceInitializer(PartialRelation linkType, PartialRelation sourceType,
27 Symbol<TruthValue> symbol) {
28 this.linkType = linkType;
29 this.sourceType = sourceType;
30 this.symbol = symbol;
31 }
32
33 @Override
34 public void initialize(Model model, ModelSeed modelSeed) {
35 var reasoningAdapter = model.getAdapter(ReasoningAdapter.class);
36 var mergedMap = getMergedMap(modelSeed);
37 var sourceRefiner = reasoningAdapter.getRefiner(sourceType);
38 var interpretation = model.getInterpretation(symbol);
39 for (var entry : mergedMap.entrySet()) {
40 var key = entry.getKey();
41 var value = entry.getValue();
42 interpretation.put(key, value);
43 if (value.must()) {
44 boolean merged = sourceRefiner.merge(Tuple.of(key.get(0)), TruthValue.TRUE) &&
45 sourceRefiner.merge(Tuple.of(key.get(1)), TruthValue.TRUE);
46 if (!merged) {
47 throw new IllegalArgumentException("Failed to merge end types of reference %s for key %s"
48 .formatted(linkType, key));
49 }
50 }
51 }
52 }
53
54 @NotNull
55 private LinkedHashMap<Tuple, TruthValue> getMergedMap(ModelSeed modelSeed) {
56 var defaultValue = symbol.defaultValue();
57 var originalMap = new LinkedHashMap<Tuple, TruthValue>();
58 var cursor = modelSeed.getCursor(linkType, defaultValue);
59 while (cursor.move()) {
60 if (originalMap.put(cursor.getKey(), cursor.getValue()) != null) {
61 throw new TranslationException(linkType, "Duplicate value for key " + cursor.getKey());
62 }
63 }
64 var mergedMap = LinkedHashMap.<Tuple, TruthValue>newLinkedHashMap(originalMap.size());
65 for (var entry : originalMap.entrySet()) {
66 var key = entry.getKey();
67 var value = entry.getValue();
68 int first = key.get(0);
69 int second = key.get(1);
70 var oppositeKey = Tuple.of(second, first);
71 var oppositeValue = originalMap.get(oppositeKey);
72 if (oppositeValue != null && second < first) {
73 // Already processed entry.
74 continue;
75 }
76 var mergedValue = value.merge(oppositeValue == null ? defaultValue : oppositeValue);
77 mergedMap.put(key, mergedValue);
78 if (first != second) {
79 mergedMap.put(oppositeKey, mergedValue);
80 }
81 }
82 return mergedMap;
83 }
84}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceTranslator.java
index c554e2a4..b76838b3 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceTranslator.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceTranslator.java
@@ -5,6 +5,7 @@
5 */ 5 */
6package tools.refinery.store.reasoning.translator.crossreference; 6package tools.refinery.store.reasoning.translator.crossreference;
7 7
8import tools.refinery.store.dse.propagation.PropagationBuilder;
8import tools.refinery.store.dse.transition.Rule; 9import tools.refinery.store.dse.transition.Rule;
9import tools.refinery.store.model.ModelStoreBuilder; 10import tools.refinery.store.model.ModelStoreBuilder;
10import tools.refinery.store.model.ModelStoreConfiguration; 11import tools.refinery.store.model.ModelStoreConfiguration;
@@ -13,15 +14,16 @@ import tools.refinery.store.query.view.ForbiddenView;
13import tools.refinery.store.reasoning.lifting.DnfLifter; 14import tools.refinery.store.reasoning.lifting.DnfLifter;
14import tools.refinery.store.reasoning.literal.Concreteness; 15import tools.refinery.store.reasoning.literal.Concreteness;
15import tools.refinery.store.reasoning.literal.Modality; 16import tools.refinery.store.reasoning.literal.Modality;
16import tools.refinery.store.reasoning.refinement.RefinementBasedInitializer;
17import tools.refinery.store.reasoning.representation.PartialRelation; 17import tools.refinery.store.reasoning.representation.PartialRelation;
18import tools.refinery.store.reasoning.translator.PartialRelationTranslator; 18import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
19import tools.refinery.store.reasoning.translator.TranslationException;
19import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator; 20import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator;
20import tools.refinery.store.representation.Symbol; 21import tools.refinery.store.representation.Symbol;
21import tools.refinery.store.representation.TruthValue; 22import tools.refinery.store.representation.TruthValue;
22 23
23import static tools.refinery.store.query.literal.Literals.not; 24import static tools.refinery.store.query.literal.Literals.not;
24import static tools.refinery.store.reasoning.actions.PartialActionLiterals.add; 25import static tools.refinery.store.reasoning.actions.PartialActionLiterals.add;
26import static tools.refinery.store.reasoning.actions.PartialActionLiterals.merge;
25import static tools.refinery.store.reasoning.literal.PartialLiterals.*; 27import static tools.refinery.store.reasoning.literal.PartialLiterals.*;
26import static tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator.MULTI_VIEW; 28import static tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator.MULTI_VIEW;
27 29
@@ -33,50 +35,77 @@ public class UndirectedCrossReferenceTranslator implements ModelStoreConfigurati
33 public UndirectedCrossReferenceTranslator(PartialRelation linkType, UndirectedCrossReferenceInfo info) { 35 public UndirectedCrossReferenceTranslator(PartialRelation linkType, UndirectedCrossReferenceInfo info) {
34 this.linkType = linkType; 36 this.linkType = linkType;
35 this.info = info; 37 this.info = info;
36 symbol = Symbol.of(linkType.name(), 2, TruthValue.class, TruthValue.UNKNOWN); 38 symbol = Symbol.of(linkType.name(), 2, TruthValue.class, info.defaultValue());
37 } 39 }
38 40
39 @Override 41 @Override
40 public void apply(ModelStoreBuilder storeBuilder) { 42 public void apply(ModelStoreBuilder storeBuilder) {
41 var name = linkType.name(); 43 var name = linkType.name();
42 var type = info.type(); 44 var type = info.type();
43 var forbiddenView = new ForbiddenView(symbol);
44 var mayName = DnfLifter.decorateName(name, Modality.MAY, Concreteness.PARTIAL); 45 var mayName = DnfLifter.decorateName(name, Modality.MAY, Concreteness.PARTIAL);
45 46
47 var defaultValue = info.defaultValue();
48 if (defaultValue.must()) {
49 throw new TranslationException(linkType, "Unsupported default value %s for directed cross references %s"
50 .formatted(defaultValue, linkType));
51 }
52
46 var mayNewSource = CrossReferenceUtils.createMayHelper(linkType, type, info.multiplicity(), false); 53 var mayNewSource = CrossReferenceUtils.createMayHelper(linkType, type, info.multiplicity(), false);
47 54
48 storeBuilder.with(PartialRelationTranslator.of(linkType) 55 var translator = PartialRelationTranslator.of(linkType);
49 .symbol(symbol) 56 translator.symbol(symbol);
50 .may(Query.of(mayName, (builder, source, target) -> { 57 if (defaultValue.may()) {
58 var forbiddenView = new ForbiddenView(symbol);
59 translator.may(Query.of(mayName, (builder, source, target) -> {
60 builder.clause(
61 mayNewSource.call(source),
62 mayNewSource.call(target),
63 not(forbiddenView.call(source, target))
64 );
65 if (info.isConstrained()) {
66 // Violation of monotonicity:
67 // Edges violating upper multiplicity will not be marked as {@code ERROR}, but the
68 // corresponding error pattern will already mark the node as invalid.
69 builder.clause(
70 must(linkType.call(source, target)),
71 not(forbiddenView.call(source, target)),
72 may(type.call(source)),
73 may(type.call(target))
74 );
75 }
76 }));
77 } else {
78 var propagationBuilder = storeBuilder.getAdapter(PropagationBuilder.class);
79 propagationBuilder.rule(Rule.of(name + "#invalidLink", (builder, p1, p2) -> {
80 builder.clause(
81 may(linkType.call(p1, p2)),
82 not(may(type.call(p1)))
83 );
84 if (info.isConstrained()) {
51 builder.clause( 85 builder.clause(
52 mayNewSource.call(source), 86 may(linkType.call(p1, p2)),
53 mayNewSource.call(target), 87 not(must(linkType.call(p1, p2))),
54 not(forbiddenView.call(source, target)) 88 not(mayNewSource.call(p1))
55 ); 89 );
56 if (info.isConstrained()) { 90 }
57 // Violation of monotonicity: 91 builder.action(
58 // Edges violating upper multiplicity will not be marked as {@code ERROR}, but the 92 merge(linkType, TruthValue.FALSE, p1, p2)
59 // corresponding error pattern will already mark the node as invalid. 93 );
60 builder.clause( 94 }));
61 must(linkType.call(source, target)), 95 }
62 not(forbiddenView.call(source, target)), 96 translator.refiner(UndirectedCrossReferenceRefiner.of(symbol, type));
63 may(type.call(source)), 97 translator.initializer(new UndirectedCrossReferenceInitializer(linkType, type, symbol));
64 may(type.call(target)) 98 translator.decision(Rule.of(linkType.name(), (builder, source, target) -> builder
65 ); 99 .clause(
66 } 100 may(linkType.call(source, target)),
67 })) 101 not(candidateMust(linkType.call(source, target))),
68 .refiner(UndirectedCrossReferenceRefiner.of(symbol, type)) 102 not(MULTI_VIEW.call(source)),
69 .initializer(new RefinementBasedInitializer<>(linkType)) 103 not(MULTI_VIEW.call(target))
70 .decision(Rule.of(linkType.name(), (builder, source, target) -> builder 104 )
71 .clause( 105 .action(
72 may(linkType.call(source, target)), 106 add(linkType, source, target)
73 not(candidateMust(linkType.call(source, target))), 107 )));
74 not(MULTI_VIEW.call(source)), 108 storeBuilder.with(translator);
75 not(MULTI_VIEW.call(target))
76 )
77 .action(
78 add(linkType, source, target)
79 ))));
80 109
81 storeBuilder.with(new InvalidMultiplicityErrorTranslator(type, linkType, false, info.multiplicity())); 110 storeBuilder.with(new InvalidMultiplicityErrorTranslator(type, linkType, false, info.multiplicity()));
82 } 111 }
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java
index 74022fc6..a5047768 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java
@@ -16,6 +16,7 @@ import tools.refinery.store.reasoning.translator.multiplicity.UnconstrainedMulti
16import tools.refinery.store.reasoning.translator.typehierarchy.TypeInfo; 16import tools.refinery.store.reasoning.translator.typehierarchy.TypeInfo;
17 17
18import java.util.*; 18import java.util.*;
19import java.util.function.Consumer;
19 20
20public class MetamodelBuilder { 21public class MetamodelBuilder {
21 private final ContainedTypeHierarchyBuilder typeHierarchyBuilder = new ContainedTypeHierarchyBuilder(); 22 private final ContainedTypeHierarchyBuilder typeHierarchyBuilder = new ContainedTypeHierarchyBuilder();
@@ -68,6 +69,12 @@ public class MetamodelBuilder {
68 return this; 69 return this;
69 } 70 }
70 71
72 public MetamodelBuilder reference(PartialRelation linkType, Consumer<ReferenceInfoBuilder> callback) {
73 var builder = ReferenceInfo.builder();
74 callback.accept(builder);
75 return reference(linkType, builder.build());
76 }
77
71 public MetamodelBuilder reference(PartialRelation linkType, ReferenceInfo info) { 78 public MetamodelBuilder reference(PartialRelation linkType, ReferenceInfo info) {
72 if (linkType.arity() != 2) { 79 if (linkType.arity() != 2) {
73 throw new TranslationException(linkType, 80 throw new TranslationException(linkType,
@@ -81,47 +88,6 @@ public class MetamodelBuilder {
81 return this; 88 return this;
82 } 89 }
83 90
84 public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, boolean containment,
85 Multiplicity multiplicity, PartialRelation targetType,
86 PartialRelation opposite) {
87 return reference(linkType, new ReferenceInfo(containment, sourceType, multiplicity, targetType, opposite));
88 }
89
90 public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, Multiplicity multiplicity,
91 PartialRelation targetType, PartialRelation opposite) {
92 return reference(linkType, sourceType, false, multiplicity, targetType, opposite);
93 }
94
95 public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType,
96 boolean containment, PartialRelation targetType, PartialRelation opposite) {
97 return reference(linkType, sourceType, containment, UnconstrainedMultiplicity.INSTANCE, targetType, opposite);
98 }
99
100 public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, PartialRelation targetType,
101 PartialRelation opposite) {
102 return reference(linkType, sourceType, UnconstrainedMultiplicity.INSTANCE, targetType, opposite);
103 }
104
105 public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, boolean containment,
106 Multiplicity multiplicity, PartialRelation targetType) {
107 return reference(linkType, sourceType, containment, multiplicity, targetType, null);
108 }
109
110 public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, Multiplicity multiplicity,
111 PartialRelation targetType) {
112 return reference(linkType, sourceType, multiplicity, targetType, null);
113 }
114
115 public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, boolean containment,
116 PartialRelation targetType) {
117 return reference(linkType, sourceType, containment, targetType, null);
118 }
119
120 public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType,
121 PartialRelation targetType) {
122 return reference(linkType, sourceType, targetType, null);
123 }
124
125 public MetamodelBuilder references(Collection<Map.Entry<PartialRelation, ReferenceInfo>> entries) { 91 public MetamodelBuilder references(Collection<Map.Entry<PartialRelation, ReferenceInfo>> entries) {
126 for (var entry : entries) { 92 for (var entry : entries) {
127 reference(entry.getKey(), entry.getValue()); 93 reference(entry.getKey(), entry.getValue());
@@ -160,10 +126,12 @@ public class MetamodelBuilder {
160 var targetType = info.targetType(); 126 var targetType = info.targetType();
161 var opposite = info.opposite(); 127 var opposite = info.opposite();
162 Multiplicity targetMultiplicity = UnconstrainedMultiplicity.INSTANCE; 128 Multiplicity targetMultiplicity = UnconstrainedMultiplicity.INSTANCE;
129 var defaultValue = info.defaultValue();
163 if (opposite != null) { 130 if (opposite != null) {
164 var oppositeInfo = referenceInfoMap.get(opposite); 131 var oppositeInfo = referenceInfoMap.get(opposite);
165 validateOpposite(linkType, info, opposite, oppositeInfo); 132 validateOpposite(linkType, info, opposite, oppositeInfo);
166 targetMultiplicity = oppositeInfo.multiplicity(); 133 targetMultiplicity = oppositeInfo.multiplicity();
134 defaultValue = defaultValue.merge(oppositeInfo.defaultValue());
167 if (oppositeInfo.containment()) { 135 if (oppositeInfo.containment()) {
168 // Skip processing this reference and process it once we encounter its containment opposite. 136 // Skip processing this reference and process it once we encounter its containment opposite.
169 return; 137 return;
@@ -175,7 +143,7 @@ public class MetamodelBuilder {
175 targetType, linkType, sourceType)); 143 targetType, linkType, sourceType));
176 } 144 }
177 undirectedCrossReferences.put(linkType, new UndirectedCrossReferenceInfo(sourceType, 145 undirectedCrossReferences.put(linkType, new UndirectedCrossReferenceInfo(sourceType,
178 info.multiplicity())); 146 info.multiplicity(), defaultValue));
179 return; 147 return;
180 } 148 }
181 oppositeReferences.put(opposite, linkType); 149 oppositeReferences.put(opposite, linkType);
@@ -185,7 +153,7 @@ public class MetamodelBuilder {
185 return; 153 return;
186 } 154 }
187 directedCrossReferences.put(linkType, new DirectedCrossReferenceInfo(sourceType, info.multiplicity(), 155 directedCrossReferences.put(linkType, new DirectedCrossReferenceInfo(sourceType, info.multiplicity(),
188 targetType, targetMultiplicity)); 156 targetType, targetMultiplicity, defaultValue));
189 } 157 }
190 158
191 private void processContainmentInfo(PartialRelation linkType, ReferenceInfo info, 159 private void processContainmentInfo(PartialRelation linkType, ReferenceInfo info,
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfo.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfo.java
index 9a6b4012..9d7fc9c3 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfo.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfo.java
@@ -7,7 +7,11 @@ package tools.refinery.store.reasoning.translator.metamodel;
7 7
8import tools.refinery.store.reasoning.representation.PartialRelation; 8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; 9import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
10import tools.refinery.store.representation.TruthValue;
10 11
11public record ReferenceInfo(boolean containment, PartialRelation sourceType, Multiplicity multiplicity, 12public record ReferenceInfo(boolean containment, PartialRelation sourceType, Multiplicity multiplicity,
12 PartialRelation targetType, PartialRelation opposite) { 13 PartialRelation targetType, PartialRelation opposite, TruthValue defaultValue) {
14 public static ReferenceInfoBuilder builder() {
15 return new ReferenceInfoBuilder();
16 }
13} 17}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfoBuilder.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfoBuilder.java
new file mode 100644
index 00000000..43d01503
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfoBuilder.java
@@ -0,0 +1,84 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.metamodel;
7
8import org.jetbrains.annotations.NotNull;
9import org.jetbrains.annotations.Nullable;
10import tools.refinery.store.reasoning.representation.PartialRelation;
11import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity;
12import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
13import tools.refinery.store.reasoning.translator.multiplicity.UnconstrainedMultiplicity;
14import tools.refinery.store.representation.TruthValue;
15import tools.refinery.store.representation.cardinality.CardinalityInterval;
16
17public final class ReferenceInfoBuilder {
18 private boolean containment;
19 private PartialRelation sourceType;
20 private Multiplicity multiplicity = UnconstrainedMultiplicity.INSTANCE;
21 private PartialRelation targetType;
22 private PartialRelation opposite;
23 private TruthValue defaultValue = TruthValue.UNKNOWN;
24
25 ReferenceInfoBuilder() {
26 }
27
28 public ReferenceInfoBuilder source(@NotNull PartialRelation sourceType) {
29 if (sourceType.arity() != 1) {
30 throw new IllegalArgumentException("Source type %s must be of arity 1".formatted(sourceType));
31 }
32 this.sourceType = sourceType;
33 return this;
34 }
35
36 public ReferenceInfoBuilder target(@NotNull PartialRelation targetType) {
37 if (targetType.arity() != 1) {
38 throw new IllegalArgumentException("Target type %s must be of arity 1".formatted(targetType));
39 }
40 this.targetType = targetType;
41 return this;
42 }
43
44 public ReferenceInfoBuilder containment(boolean containment) {
45 this.containment = containment;
46 return this;
47 }
48
49 public ReferenceInfoBuilder multiplicity(@NotNull Multiplicity multiplicity) {
50 this.multiplicity = multiplicity;
51 return this;
52 }
53
54 public ReferenceInfoBuilder multiplicity(@NotNull CardinalityInterval multiplicityInterval,
55 @NotNull PartialRelation errorSymbol) {
56 return multiplicity(ConstrainedMultiplicity.of(multiplicityInterval, errorSymbol));
57 }
58
59 public ReferenceInfoBuilder opposite(@Nullable PartialRelation opposite) {
60 if (opposite != null && opposite.arity() != 2) {
61 throw new IllegalArgumentException("Opposite %s must be of arity 2".formatted(targetType));
62 }
63 this.opposite = opposite;
64 return this;
65 }
66
67 public ReferenceInfoBuilder defaultValue(@NotNull TruthValue defaultValue) {
68 if (defaultValue.must()) {
69 throw new IllegalArgumentException("Unsupported default value");
70 }
71 this.defaultValue = defaultValue;
72 return this;
73 }
74
75 public ReferenceInfo build() {
76 if (sourceType == null) {
77 throw new IllegalStateException("Source type is required");
78 }
79 if (targetType == null) {
80 throw new IllegalStateException("Target type is required");
81 }
82 return new ReferenceInfo(containment, sourceType, multiplicity, targetType, opposite, defaultValue);
83 }
84}
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java
index 0f1a1006..ba6ba6da 100644
--- a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java
+++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java
@@ -8,7 +8,6 @@ package tools.refinery.store.reasoning.translator.metamodel;
8import org.junit.jupiter.api.Test; 8import org.junit.jupiter.api.Test;
9import tools.refinery.store.reasoning.representation.PartialRelation; 9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.reasoning.translator.TranslationException; 10import tools.refinery.store.reasoning.translator.TranslationException;
11import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity;
12import tools.refinery.store.representation.cardinality.CardinalityIntervals; 11import tools.refinery.store.representation.cardinality.CardinalityIntervals;
13 12
14import static org.junit.jupiter.api.Assertions.assertThrows; 13import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -24,8 +23,13 @@ class MetamodelBuilderTest {
24 var builder = Metamodel.builder() 23 var builder = Metamodel.builder()
25 .type(university) 24 .type(university)
26 .type(course) 25 .type(course)
27 .reference(courses, university, course, location) 26 .reference(courses, referenceBuilder -> referenceBuilder
28 .reference(location, course, university); 27 .source(university)
28 .target(course)
29 .opposite(location))
30 .reference(location, referenceBuilder -> referenceBuilder
31 .source(course)
32 .target(university));
29 33
30 assertThrows(TranslationException.class, builder::build); 34 assertThrows(TranslationException.class, builder::build);
31 } 35 }
@@ -35,8 +39,14 @@ class MetamodelBuilderTest {
35 var builder = Metamodel.builder() 39 var builder = Metamodel.builder()
36 .type(university) 40 .type(university)
37 .type(course) 41 .type(course)
38 .reference(courses, university, course, location) 42 .reference(courses, referenceBuilder -> referenceBuilder
39 .reference(location, course, course, courses); 43 .source(university)
44 .target(course)
45 .opposite(location))
46 .reference(location, referenceBuilder -> referenceBuilder
47 .source(course)
48 .target(course)
49 .opposite(courses));
40 50
41 assertThrows(TranslationException.class, builder::build); 51 assertThrows(TranslationException.class, builder::build);
42 } 52 }
@@ -48,10 +58,16 @@ class MetamodelBuilderTest {
48 var builder = Metamodel.builder() 58 var builder = Metamodel.builder()
49 .type(university) 59 .type(university)
50 .type(course) 60 .type(course)
51 .reference(courses, university, true, course, location) 61 .reference(courses, referenceBuilder -> referenceBuilder
52 .reference(location, course, 62 .containment(true)
53 ConstrainedMultiplicity.of(CardinalityIntervals.atLeast(2), invalidMultiplicity), 63 .source(university)
54 university, courses); 64 .target(course)
65 .opposite(location))
66 .reference(location, referenceBuilder -> referenceBuilder
67 .source(course)
68 .multiplicity(CardinalityIntervals.atLeast(2), invalidMultiplicity)
69 .target(university)
70 .opposite(courses));
55 71
56 assertThrows(TranslationException.class, builder::build); 72 assertThrows(TranslationException.class, builder::build);
57 } 73 }
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTest.java
index 6c2f55af..88e6a4d7 100644
--- a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTest.java
+++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTest.java
@@ -16,7 +16,6 @@ import tools.refinery.store.reasoning.representation.PartialRelation;
16import tools.refinery.store.reasoning.seed.ModelSeed; 16import tools.refinery.store.reasoning.seed.ModelSeed;
17import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator; 17import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator;
18import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator; 18import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
19import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity;
20import tools.refinery.store.representation.TruthValue; 19import tools.refinery.store.representation.TruthValue;
21import tools.refinery.store.representation.cardinality.CardinalityIntervals; 20import tools.refinery.store.representation.cardinality.CardinalityIntervals;
22import tools.refinery.store.tuple.Tuple; 21import tools.refinery.store.tuple.Tuple;
@@ -45,12 +44,23 @@ class MetamodelTest {
45 .type(teacher, person) 44 .type(teacher, person)
46 .type(university) 45 .type(university)
47 .type(course) 46 .type(course)
48 .reference(courses, university, true, course, location) 47 .reference(courses, builder -> builder
49 .reference(location, course, university, courses) 48 .containment(true)
50 .reference(lecturer, course, 49 .source(university)
51 ConstrainedMultiplicity.of(CardinalityIntervals.ONE, invalidLecturerCount), teacher) 50 .target(course)
52 .reference(enrolledStudents, course, 51 .opposite(location))
53 ConstrainedMultiplicity.of(CardinalityIntervals.SOME, invalidStudentCount), student) 52 .reference(location, builder -> builder
53 .source(course)
54 .target(university)
55 .opposite(courses))
56 .reference(lecturer, builder -> builder
57 .source(course)
58 .multiplicity(CardinalityIntervals.ONE, invalidLecturerCount)
59 .target(teacher))
60 .reference(enrolledStudents, builder -> builder
61 .source(course)
62 .multiplicity(CardinalityIntervals.SOME, invalidStudentCount)
63 .target(student))
54 .build(); 64 .build();
55 65
56 var seed = ModelSeed.builder(5) 66 var seed = ModelSeed.builder(5)
@@ -105,10 +115,12 @@ class MetamodelTest {
105 var metamodel = Metamodel.builder() 115 var metamodel = Metamodel.builder()
106 .type(university) 116 .type(university)
107 .type(course) 117 .type(course)
108 .reference(courses, university, true, course) 118 .reference(courses, builder -> builder
119 .containment(true)
120 .source(university)
121 .target(course))
109 .build(); 122 .build();
110 123
111
112 var seed = ModelSeed.builder(4) 124 var seed = ModelSeed.builder(4)
113 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder 125 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
114 .reducedValue(CardinalityIntervals.ONE) 126 .reducedValue(CardinalityIntervals.ONE)