aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2023-09-03 17:57:38 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2023-09-03 17:57:38 +0200
commitcd96a9a4f54d45cda3ddf5df474946445d557090 (patch)
tree7a96a177236888ede9a51ffdd51940a672cfd070 /subprojects
parentbuild: runtimeOnly Eclipse Collections if posible (diff)
downloadrefinery-cd96a9a4f54d45cda3ddf5df474946445d557090.tar.gz
refinery-cd96a9a4f54d45cda3ddf5df474946445d557090.tar.zst
refinery-cd96a9a4f54d45cda3ddf5df474946445d557090.zip
feat: scope propagator in language
Diffstat (limited to 'subprojects')
-rw-r--r--subprojects/frontend/src/editor/EditorTheme.ts2
-rw-r--r--subprojects/frontend/src/graph/dotSource.ts16
-rw-r--r--subprojects/language-semantics/build.gradle.kts1
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java69
-rw-r--r--subprojects/language-web/build.gradle.kts1
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java26
-rw-r--r--subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/MultiView.java23
-rw-r--r--subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorAdapterImpl.java7
-rw-r--r--subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorBuilderImpl.java18
-rw-r--r--subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/MPSolverTest.java31
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/NonEmptyCardinalityInterval.java7
11 files changed, 165 insertions, 36 deletions
diff --git a/subprojects/frontend/src/editor/EditorTheme.ts b/subprojects/frontend/src/editor/EditorTheme.ts
index 308d5be0..055b62e2 100644
--- a/subprojects/frontend/src/editor/EditorTheme.ts
+++ b/subprojects/frontend/src/editor/EditorTheme.ts
@@ -105,7 +105,7 @@ export default styled('div', {
105 color: theme.palette.text.primary, 105 color: theme.palette.text.primary,
106 }, 106 },
107 }, 107 },
108 '.tok-problem-abstract, .tok-problem-new': { 108 '.tok-problem-abstract': {
109 fontStyle: 'italic', 109 fontStyle: 'italic',
110 }, 110 },
111 '.tok-problem-containment': { 111 '.tok-problem-containment': {
diff --git a/subprojects/frontend/src/graph/dotSource.ts b/subprojects/frontend/src/graph/dotSource.ts
index 963a9663..b24bca2f 100644
--- a/subprojects/frontend/src/graph/dotSource.ts
+++ b/subprojects/frontend/src/graph/dotSource.ts
@@ -20,8 +20,6 @@ function nodeName(graph: GraphStore, metadata: NodeMetadata): string {
20 switch (metadata.kind) { 20 switch (metadata.kind) {
21 case 'INDIVIDUAL': 21 case 'INDIVIDUAL':
22 return `<b>${name}</b>`; 22 return `<b>${name}</b>`;
23 case 'NEW':
24 return `<i>${name}</i>`;
25 default: 23 default:
26 return name; 24 return name;
27 } 25 }
@@ -44,6 +42,7 @@ interface NodeData {
44 exists: string; 42 exists: string;
45 equalsSelf: string; 43 equalsSelf: string;
46 unaryPredicates: Map<RelationMetadata, string>; 44 unaryPredicates: Map<RelationMetadata, string>;
45 count: string;
47} 46}
48 47
49function computeNodeData(graph: GraphStore): NodeData[] { 48function computeNodeData(graph: GraphStore): NodeData[] {
@@ -56,6 +55,7 @@ function computeNodeData(graph: GraphStore): NodeData[] {
56 exists: 'FALSE', 55 exists: 'FALSE',
57 equalsSelf: 'FALSE', 56 equalsSelf: 'FALSE',
58 unaryPredicates: new Map(), 57 unaryPredicates: new Map(),
58 count: '[0]',
59 })); 59 }));
60 60
61 relations.forEach((relation) => { 61 relations.forEach((relation) => {
@@ -107,6 +107,15 @@ function computeNodeData(graph: GraphStore): NodeData[] {
107 } 107 }
108 }); 108 });
109 109
110 partialInterpretation['builtin::count']?.forEach(([index, value]) => {
111 if (typeof index === 'number' && typeof value === 'string') {
112 const data = nodeData[index];
113 if (data !== undefined) {
114 data.count = value;
115 }
116 }
117 });
118
110 return nodeData; 119 return nodeData;
111} 120}
112 121
@@ -132,9 +141,10 @@ function createNodes(graph: GraphStore, lines: string[]): void {
132 const classes = classList.join(' '); 141 const classes = classList.join(' ');
133 const name = nodeName(graph, node); 142 const name = nodeName(graph, node);
134 const border = node.kind === 'INDIVIDUAL' ? 2 : 1; 143 const border = node.kind === 'INDIVIDUAL' ? 2 : 1;
144 const count = data.equalsSelf !== 'TRUE' ? ` ${data.count}` : '';
135 lines.push(`n${i} [id="${node.name}", class="${classes}", label=< 145 lines.push(`n${i} [id="${node.name}", class="${classes}", label=<
136 <table border="${border}" cellborder="0" cellspacing="0" style="rounded" bgcolor="white"> 146 <table border="${border}" cellborder="0" cellspacing="0" style="rounded" bgcolor="white">
137 <tr><td cellpadding="4.5" width="32" bgcolor="green">${name}</td></tr>`); 147 <tr><td cellpadding="4.5" width="32" bgcolor="green">${name}${count}</td></tr>`);
138 if (data.unaryPredicates.size > 0) { 148 if (data.unaryPredicates.size > 0) {
139 lines.push( 149 lines.push(
140 '<hr/><tr><td cellpadding="4.5"><table fixedsize="TRUE" align="left" border="0" cellborder="0" cellspacing="0" cellpadding="1.5">', 150 '<hr/><tr><td cellpadding="4.5"><table fixedsize="TRUE" align="left" border="0" cellborder="0" cellspacing="0" cellpadding="1.5">',
diff --git a/subprojects/language-semantics/build.gradle.kts b/subprojects/language-semantics/build.gradle.kts
index b6f3f709..4374f78c 100644
--- a/subprojects/language-semantics/build.gradle.kts
+++ b/subprojects/language-semantics/build.gradle.kts
@@ -14,5 +14,6 @@ dependencies {
14 api(project(":refinery-store")) 14 api(project(":refinery-store"))
15 api(project(":refinery-store-query")) 15 api(project(":refinery-store-query"))
16 api(project(":refinery-store-reasoning")) 16 api(project(":refinery-store-reasoning"))
17 implementation(project(":refinery-store-reasoning-scope"))
17 runtimeOnly(libs.eclipseCollections) 18 runtimeOnly(libs.eclipseCollections)
18} 19}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java
index 13e25d0a..89c41a8e 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java
@@ -24,6 +24,7 @@ import tools.refinery.store.query.term.Variable;
24import tools.refinery.store.reasoning.ReasoningAdapter; 24import tools.refinery.store.reasoning.ReasoningAdapter;
25import tools.refinery.store.reasoning.representation.AnyPartialSymbol; 25import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
26import tools.refinery.store.reasoning.representation.PartialRelation; 26import tools.refinery.store.reasoning.representation.PartialRelation;
27import tools.refinery.store.reasoning.scope.ScopePropagatorBuilder;
27import tools.refinery.store.reasoning.seed.ModelSeed; 28import tools.refinery.store.reasoning.seed.ModelSeed;
28import tools.refinery.store.reasoning.seed.Seed; 29import tools.refinery.store.reasoning.seed.Seed;
29import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator; 30import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator;
@@ -72,6 +73,8 @@ public class ModelInitializer {
72 73
73 private Metamodel metamodel; 74 private Metamodel metamodel;
74 75
76 private Map<Tuple, CardinalityInterval> countSeed = new LinkedHashMap<>();
77
75 private ModelSeed modelSeed; 78 private ModelSeed modelSeed;
76 79
77 public Problem getProblem() { 80 public Problem getProblem() {
@@ -135,6 +138,10 @@ public class ModelInitializer {
135 relationTrace.put(relation, partialRelation); 138 relationTrace.put(relation, partialRelation);
136 modelSeedBuilder.seed(partialRelation, info.toSeed(nodeCount)); 139 modelSeedBuilder.seed(partialRelation, info.toSeed(nodeCount));
137 } 140 }
141 collectScopes();
142 modelSeedBuilder.seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
143 .reducedValue(CardinalityIntervals.SET)
144 .putAll(countSeed));
138 modelSeed = modelSeedBuilder.build(); 145 modelSeed = modelSeedBuilder.build();
139 collectPredicates(); 146 collectPredicates();
140 return modelSeed; 147 return modelSeed;
@@ -288,20 +295,26 @@ public class ModelInitializer {
288 CardinalityInterval interval; 295 CardinalityInterval interval;
289 if (problemMultiplicity == null) { 296 if (problemMultiplicity == null) {
290 interval = CardinalityIntervals.LONE; 297 interval = CardinalityIntervals.LONE;
291 } else if (problemMultiplicity instanceof ExactMultiplicity exactMultiplicity) { 298 } else {
292 interval = CardinalityIntervals.exactly(exactMultiplicity.getExactValue()); 299 interval = getCardinalityInterval(problemMultiplicity);
300 }
301 var constraint = getRelationInfo(referenceDeclaration.getInvalidMultiplicity()).partialRelation();
302 return ConstrainedMultiplicity.of(interval, constraint);
303 }
304
305 private static CardinalityInterval getCardinalityInterval(
306 tools.refinery.language.model.problem.Multiplicity problemMultiplicity) {
307 if (problemMultiplicity instanceof ExactMultiplicity exactMultiplicity) {
308 return CardinalityIntervals.exactly(exactMultiplicity.getExactValue());
293 } else if (problemMultiplicity instanceof RangeMultiplicity rangeMultiplicity) { 309 } else if (problemMultiplicity instanceof RangeMultiplicity rangeMultiplicity) {
294 var upperBound = rangeMultiplicity.getUpperBound(); 310 var upperBound = rangeMultiplicity.getUpperBound();
295 interval = CardinalityIntervals.between(rangeMultiplicity.getLowerBound(), 311 return CardinalityIntervals.between(rangeMultiplicity.getLowerBound(),
296 upperBound < 0 ? UpperCardinalities.UNBOUNDED : UpperCardinalities.atMost(upperBound)); 312 upperBound < 0 ? UpperCardinalities.UNBOUNDED : UpperCardinalities.atMost(upperBound));
297 } else { 313 } else {
298 throw new TracedException(problemMultiplicity, "Unknown multiplicity"); 314 throw new TracedException(problemMultiplicity, "Unknown multiplicity");
299 } 315 }
300 var constraint = getRelationInfo(referenceDeclaration.getInvalidMultiplicity()).partialRelation();
301 return ConstrainedMultiplicity.of(interval, constraint);
302 } 316 }
303 317
304
305 private void collectAssertions() { 318 private void collectAssertions() {
306 for (var statement : problem.getStatements()) { 319 for (var statement : problem.getStatements()) {
307 if (statement instanceof ClassDeclaration classDeclaration) { 320 if (statement instanceof ClassDeclaration classDeclaration) {
@@ -598,6 +611,50 @@ public class ModelInitializer {
598 return argumentList; 611 return argumentList;
599 } 612 }
600 613
614 private void collectScopes() {
615 for (var statement : problem.getStatements()) {
616 if (statement instanceof ScopeDeclaration scopeDeclaration) {
617 for (var typeScope : scopeDeclaration.getTypeScopes()) {
618 if (typeScope.isIncrement()) {
619 collectTypeScopeIncrement(typeScope);
620 } else {
621 collectTypeScope(typeScope);
622 }
623 }
624 }
625 }
626 }
627
628 private void collectTypeScopeIncrement(TypeScope typeScope) {
629 if (!(typeScope.getTargetType() instanceof ClassDeclaration classDeclaration)) {
630 throw new TracedException(typeScope, "Target of incremental type scope must be a class declaration");
631 }
632 var newNode = classDeclaration.getNewNode();
633 if (newNode == null) {
634 throw new TracedException(typeScope, "Target of incremental type scope must be concrete class");
635 }
636 int newNodeId = nodeTrace.get(newNode);
637 var type = relationTrace.get(classDeclaration);
638 var typeInfo = metamodel.typeHierarchy().getAnalysisResult(type);
639 if (!typeInfo.getDirectSubtypes().isEmpty()) {
640 throw new TracedException(typeScope, "Target of incremental type scope cannot have any subclasses");
641 }
642 var interval = getCardinalityInterval(typeScope.getMultiplicity());
643 countSeed.compute(Tuple.of(newNodeId), (key, oldValue) ->
644 oldValue == null ? interval : oldValue.meet(interval));
645 }
646
647 private void collectTypeScope(TypeScope typeScope) {
648 var scopePropagatorBuilder = storeBuilder.tryGetAdapter(ScopePropagatorBuilder.class).orElseThrow(
649 () -> new TracedException(typeScope, "Type scopes require a ScopePropagatorBuilder"));
650 var type = relationTrace.get(typeScope.getTargetType());
651 if (type == null) {
652 throw new TracedException(typeScope, "Unknown target type");
653 }
654 var interval = getCardinalityInterval(typeScope.getMultiplicity());
655 scopePropagatorBuilder.scope(type, interval);
656 }
657
601 private record RelationInfo(PartialRelation partialRelation, MutableSeed<TruthValue> assertions, 658 private record RelationInfo(PartialRelation partialRelation, MutableSeed<TruthValue> assertions,
602 MutableSeed<TruthValue> defaultAssertions) { 659 MutableSeed<TruthValue> defaultAssertions) {
603 public RelationInfo(String name, int arity, TruthValue value, TruthValue defaultValue) { 660 public RelationInfo(String name, int arity, TruthValue value, TruthValue defaultValue) {
diff --git a/subprojects/language-web/build.gradle.kts b/subprojects/language-web/build.gradle.kts
index a4ccdd9f..9f772d41 100644
--- a/subprojects/language-web/build.gradle.kts
+++ b/subprojects/language-web/build.gradle.kts
@@ -19,6 +19,7 @@ dependencies {
19 implementation(project(":refinery-language-ide")) 19 implementation(project(":refinery-language-ide"))
20 implementation(project(":refinery-language-semantics")) 20 implementation(project(":refinery-language-semantics"))
21 implementation(project(":refinery-store-query-viatra")) 21 implementation(project(":refinery-store-query-viatra"))
22 implementation(project(":refinery-store-reasoning-scope"))
22 implementation(libs.gson) 23 implementation(libs.gson)
23 implementation(libs.jetty.server) 24 implementation(libs.jetty.server)
24 implementation(libs.jetty.servlet) 25 implementation(libs.jetty.servlet)
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java
index 108b87dc..8470bb99 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java
@@ -22,15 +22,18 @@ import tools.refinery.language.semantics.metadata.MetadataCreator;
22import tools.refinery.language.semantics.model.ModelInitializer; 22import tools.refinery.language.semantics.model.ModelInitializer;
23import tools.refinery.language.semantics.model.SemanticsUtils; 23import tools.refinery.language.semantics.model.SemanticsUtils;
24import tools.refinery.language.semantics.model.TracedException; 24import tools.refinery.language.semantics.model.TracedException;
25import tools.refinery.store.map.Cursor;
25import tools.refinery.store.model.Model; 26import tools.refinery.store.model.Model;
26import tools.refinery.store.model.ModelStore; 27import tools.refinery.store.model.ModelStore;
27import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; 28import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
28import tools.refinery.store.reasoning.ReasoningAdapter; 29import tools.refinery.store.reasoning.ReasoningAdapter;
29import tools.refinery.store.reasoning.ReasoningStoreAdapter; 30import tools.refinery.store.reasoning.ReasoningStoreAdapter;
30import tools.refinery.store.reasoning.literal.Concreteness; 31import tools.refinery.store.reasoning.literal.Concreteness;
32import tools.refinery.store.reasoning.refinement.RefinementResult;
31import tools.refinery.store.reasoning.representation.PartialRelation; 33import tools.refinery.store.reasoning.representation.PartialRelation;
34import tools.refinery.store.reasoning.scope.ScopePropagatorAdapter;
32import tools.refinery.store.reasoning.translator.TranslationException; 35import tools.refinery.store.reasoning.translator.TranslationException;
33import tools.refinery.store.representation.TruthValue; 36import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
34import tools.refinery.store.tuple.Tuple; 37import tools.refinery.store.tuple.Tuple;
35import tools.refinery.viatra.runtime.CancellationToken; 38import tools.refinery.viatra.runtime.CancellationToken;
36 39
@@ -75,7 +78,8 @@ class SemanticsWorker implements Callable<SemanticsResult> {
75 .with(ViatraModelQueryAdapter.builder() 78 .with(ViatraModelQueryAdapter.builder()
76 .cancellationToken(cancellationToken)) 79 .cancellationToken(cancellationToken))
77 .with(ReasoningAdapter.builder() 80 .with(ReasoningAdapter.builder()
78 .requiredInterpretations(Concreteness.PARTIAL)); 81 .requiredInterpretations(Concreteness.PARTIAL))
82 .with(ScopePropagatorAdapter.builder());
79 cancellationToken.checkCancelled(); 83 cancellationToken.checkCancelled();
80 try { 84 try {
81 var modelSeed = initializer.createModel(problem, builder); 85 var modelSeed = initializer.createModel(problem, builder);
@@ -90,6 +94,9 @@ class SemanticsWorker implements Callable<SemanticsResult> {
90 cancellationToken.checkCancelled(); 94 cancellationToken.checkCancelled();
91 var cancellableModelSeed = CancellableSeed.wrap(cancellationToken, modelSeed); 95 var cancellableModelSeed = CancellableSeed.wrap(cancellationToken, modelSeed);
92 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(cancellableModelSeed); 96 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(cancellableModelSeed);
97 if (model.getAdapter(ScopePropagatorAdapter.class).propagate() == RefinementResult.REJECTED) {
98 return new SemanticsInternalErrorResult("Scopes are unsatisfiable");
99 }
93 cancellationToken.checkCancelled(); 100 cancellationToken.checkCancelled();
94 var partialInterpretation = getPartialInterpretation(initializer, model); 101 var partialInterpretation = getPartialInterpretation(initializer, model);
95 102
@@ -113,13 +120,18 @@ class SemanticsWorker implements Callable<SemanticsResult> {
113 json.add(name, tuples); 120 json.add(name, tuples);
114 cancellationToken.checkCancelled(); 121 cancellationToken.checkCancelled();
115 } 122 }
123 json.add("builtin::count", getCountJson(model));
116 return json; 124 return json;
117 } 125 }
118 126
119 private static JsonArray getTuplesJson(ReasoningAdapter adapter, PartialRelation partialSymbol) { 127 private static JsonArray getTuplesJson(ReasoningAdapter adapter, PartialRelation partialSymbol) {
120 var interpretation = adapter.getPartialInterpretation(Concreteness.PARTIAL, partialSymbol); 128 var interpretation = adapter.getPartialInterpretation(Concreteness.PARTIAL, partialSymbol);
121 var cursor = interpretation.getAll(); 129 var cursor = interpretation.getAll();
122 var map = new TreeMap<Tuple, TruthValue>(); 130 return getTuplesJson(cursor);
131 }
132
133 private static JsonArray getTuplesJson(Cursor<Tuple, ?> cursor) {
134 var map = new TreeMap<Tuple, Object>();
123 while (cursor.move()) { 135 while (cursor.move()) {
124 map.put(cursor.getKey(), cursor.getValue()); 136 map.put(cursor.getKey(), cursor.getValue());
125 } 137 }
@@ -130,7 +142,7 @@ class SemanticsWorker implements Callable<SemanticsResult> {
130 return tuples; 142 return tuples;
131 } 143 }
132 144
133 private static JsonArray toArray(Tuple tuple, TruthValue value) { 145 private static JsonArray toArray(Tuple tuple, Object value) {
134 int arity = tuple.getSize(); 146 int arity = tuple.getSize();
135 var json = new JsonArray(arity + 1); 147 var json = new JsonArray(arity + 1);
136 for (int i = 0; i < arity; i++) { 148 for (int i = 0; i < arity; i++) {
@@ -140,6 +152,12 @@ class SemanticsWorker implements Callable<SemanticsResult> {
140 return json; 152 return json;
141 } 153 }
142 154
155 private static JsonArray getCountJson(Model model) {
156 var interpretation = model.getInterpretation(MultiObjectTranslator.COUNT_STORAGE);
157 var cursor = interpretation.getAll();
158 return getTuplesJson(cursor);
159 }
160
143 private SemanticsResult getTracedErrorResult(EObject sourceElement, String message) { 161 private SemanticsResult getTracedErrorResult(EObject sourceElement, String message) {
144 if (sourceElement == null || !problem.eResource().equals(sourceElement.eResource())) { 162 if (sourceElement == null || !problem.eResource().equals(sourceElement.eResource())) {
145 return new SemanticsInternalErrorResult(message); 163 return new SemanticsInternalErrorResult(message);
diff --git a/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/MultiView.java b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/MultiView.java
new file mode 100644
index 00000000..cea4e07d
--- /dev/null
+++ b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/MultiView.java
@@ -0,0 +1,23 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.scope.internal;
7
8import tools.refinery.store.query.view.TuplePreservingView;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.representation.cardinality.CardinalityInterval;
11import tools.refinery.store.representation.cardinality.CardinalityIntervals;
12import tools.refinery.store.tuple.Tuple;
13
14class MultiView extends TuplePreservingView<CardinalityInterval> {
15 protected MultiView(Symbol<CardinalityInterval> symbol) {
16 super(symbol, "multi");
17 }
18
19 @Override
20 protected boolean doFilter(Tuple key, CardinalityInterval value) {
21 return !CardinalityIntervals.ONE.equals(value);
22 }
23}
diff --git a/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorAdapterImpl.java b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorAdapterImpl.java
index 0d594701..c257df6b 100644
--- a/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorAdapterImpl.java
+++ b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorAdapterImpl.java
@@ -210,10 +210,9 @@ class ScopePropagatorAdapterImpl implements ScopePropagatorAdapter {
210 UpperCardinality upperBound; 210 UpperCardinality upperBound;
211 switch (maximizationResult) { 211 switch (maximizationResult) {
212 case OPTIMAL -> upperBound = UpperCardinalities.atMost(RoundingUtil.roundDown(objective.value())); 212 case OPTIMAL -> upperBound = UpperCardinalities.atMost(RoundingUtil.roundDown(objective.value()));
213 case UNBOUNDED -> upperBound = UpperCardinalities.UNBOUNDED; 213 // Problem was feasible when minimizing, the only possible source of {@code UNBOUNDED_OR_INFEASIBLE} is
214 case INFEASIBLE -> { 214 // an unbounded maximization problem. See https://github.com/google/or-tools/issues/3319
215 return RefinementResult.REJECTED; 215 case UNBOUNDED, INFEASIBLE -> upperBound = UpperCardinalities.UNBOUNDED;
216 }
217 default -> throw new IllegalStateException("Failed to solve for maximum of %s: %s" 216 default -> throw new IllegalStateException("Failed to solve for maximum of %s: %s"
218 .formatted(variable, minimizationResult)); 217 .formatted(variable, minimizationResult));
219 } 218 }
diff --git a/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorBuilderImpl.java b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorBuilderImpl.java
index f383ebeb..11ca7381 100644
--- a/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorBuilderImpl.java
+++ b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorBuilderImpl.java
@@ -22,12 +22,6 @@ import tools.refinery.store.representation.cardinality.FiniteUpperCardinality;
22 22
23import java.util.*; 23import java.util.*;
24 24
25import static tools.refinery.store.query.literal.Literals.not;
26import static tools.refinery.store.reasoning.ReasoningAdapter.EQUALS_SYMBOL;
27import static tools.refinery.store.reasoning.ReasoningAdapter.EXISTS_SYMBOL;
28import static tools.refinery.store.reasoning.literal.PartialLiterals.may;
29import static tools.refinery.store.reasoning.literal.PartialLiterals.must;
30
31public class ScopePropagatorBuilderImpl extends AbstractModelAdapterBuilder<ScopePropagatorStoreAdapter> 25public class ScopePropagatorBuilderImpl extends AbstractModelAdapterBuilder<ScopePropagatorStoreAdapter>
32 implements ScopePropagatorBuilder { 26 implements ScopePropagatorBuilder {
33 private Symbol<CardinalityInterval> countSymbol = MultiObjectTranslator.COUNT_STORAGE; 27 private Symbol<CardinalityInterval> countSymbol = MultiObjectTranslator.COUNT_STORAGE;
@@ -66,16 +60,8 @@ public class ScopePropagatorBuilderImpl extends AbstractModelAdapterBuilder<Scop
66 60
67 @Override 61 @Override
68 protected void doConfigure(ModelStoreBuilder storeBuilder) { 62 protected void doConfigure(ModelStoreBuilder storeBuilder) {
69 var multiQuery = Query.of("MULTI", (builder, instance) -> builder 63 var multiQuery = Query.of("MULTI", (builder, instance) -> builder.clause(
70 .clause( 64 new MultiView(countSymbol).call(instance)));
71 may(EXISTS_SYMBOL.call(instance)),
72 not(must(EXISTS_SYMBOL.call(instance)))
73 )
74 .clause(
75 may(EXISTS_SYMBOL.call(instance)),
76 not(must(EQUALS_SYMBOL.call(instance, instance)))
77 )
78 );
79 typeScopePropagatorFactories = new ArrayList<>(scopes.size()); 65 typeScopePropagatorFactories = new ArrayList<>(scopes.size());
80 for (var entry : scopes.entrySet()) { 66 for (var entry : scopes.entrySet()) {
81 var type = entry.getKey(); 67 var type = entry.getKey();
diff --git a/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/MPSolverTest.java b/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/MPSolverTest.java
index c9745d22..95c4ac68 100644
--- a/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/MPSolverTest.java
+++ b/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/MPSolverTest.java
@@ -49,4 +49,35 @@ class MPSolverTest {
49 assertThat(solver.solve(), is(MPSolver.ResultStatus.OPTIMAL)); 49 assertThat(solver.solve(), is(MPSolver.ResultStatus.OPTIMAL));
50 assertThat(objective.value(), closeTo(1, 0.01)); 50 assertThat(objective.value(), closeTo(1, 0.01));
51 } 51 }
52
53 @Test
54 void unboundedIsInfeasibleTest() {
55 var solver = MPSolver.createSolver("GLOP");
56 var x = solver.makeNumVar(0, Double.POSITIVE_INFINITY, "x");
57 var objective = solver.objective();
58 objective.setCoefficient(x, 1);
59
60 objective.setMinimization();
61 assertThat(solver.solve(), is(MPSolver.ResultStatus.OPTIMAL));
62 assertThat(objective.value(), closeTo(0, 0.01));
63
64 objective.setMaximization();
65 assertThat(solver.solve(), is(MPSolver.ResultStatus.INFEASIBLE));
66 }
67
68 @Test
69 void constantTest() {
70 var solver = MPSolver.createSolver("GLOP");
71 var x = solver.makeNumVar(1, 1, "x");
72 var objective = solver.objective();
73 objective.setCoefficient(x, 1);
74
75 objective.setMinimization();
76 assertThat(solver.solve(), is(MPSolver.ResultStatus.OPTIMAL));
77 assertThat(objective.value(), closeTo(1, 0.01));
78
79 objective.setMaximization();
80 assertThat(solver.solve(), is(MPSolver.ResultStatus.OPTIMAL));
81 assertThat(objective.value(), closeTo(1, 0.01));
82 }
52} 83}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/NonEmptyCardinalityInterval.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/NonEmptyCardinalityInterval.java
index 2e7780da..bfaeea25 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/NonEmptyCardinalityInterval.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/NonEmptyCardinalityInterval.java
@@ -83,7 +83,10 @@ public record NonEmptyCardinalityInterval(int lowerBound, UpperCardinality upper
83 83
84 @Override 84 @Override
85 public String toString() { 85 public String toString() {
86 var closeBracket = upperBound instanceof UnboundedUpperCardinality ? ")" : "]"; 86 if (upperBound instanceof FiniteUpperCardinality finiteUpperCardinality &&
87 return "[%d..%s%s".formatted(lowerBound, upperBound, closeBracket); 87 finiteUpperCardinality.finiteUpperBound() == lowerBound) {
88 return "[%d]".formatted(lowerBound);
89 }
90 return "[%d..%s]".formatted(lowerBound, upperBound);
88 } 91 }
89} 92}