diff options
author | 2023-09-03 17:57:38 +0200 | |
---|---|---|
committer | 2023-09-03 17:57:38 +0200 | |
commit | cd96a9a4f54d45cda3ddf5df474946445d557090 (patch) | |
tree | 7a96a177236888ede9a51ffdd51940a672cfd070 /subprojects | |
parent | build: runtimeOnly Eclipse Collections if posible (diff) | |
download | refinery-cd96a9a4f54d45cda3ddf5df474946445d557090.tar.gz refinery-cd96a9a4f54d45cda3ddf5df474946445d557090.tar.zst refinery-cd96a9a4f54d45cda3ddf5df474946445d557090.zip |
feat: scope propagator in language
Diffstat (limited to 'subprojects')
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 | ||
49 | function computeNodeData(graph: GraphStore): NodeData[] { | 48 | function 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; | |||
24 | import tools.refinery.store.reasoning.ReasoningAdapter; | 24 | import tools.refinery.store.reasoning.ReasoningAdapter; |
25 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; | 25 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; |
26 | import tools.refinery.store.reasoning.representation.PartialRelation; | 26 | import tools.refinery.store.reasoning.representation.PartialRelation; |
27 | import tools.refinery.store.reasoning.scope.ScopePropagatorBuilder; | ||
27 | import tools.refinery.store.reasoning.seed.ModelSeed; | 28 | import tools.refinery.store.reasoning.seed.ModelSeed; |
28 | import tools.refinery.store.reasoning.seed.Seed; | 29 | import tools.refinery.store.reasoning.seed.Seed; |
29 | import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator; | 30 | import 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; | |||
22 | import tools.refinery.language.semantics.model.ModelInitializer; | 22 | import tools.refinery.language.semantics.model.ModelInitializer; |
23 | import tools.refinery.language.semantics.model.SemanticsUtils; | 23 | import tools.refinery.language.semantics.model.SemanticsUtils; |
24 | import tools.refinery.language.semantics.model.TracedException; | 24 | import tools.refinery.language.semantics.model.TracedException; |
25 | import tools.refinery.store.map.Cursor; | ||
25 | import tools.refinery.store.model.Model; | 26 | import tools.refinery.store.model.Model; |
26 | import tools.refinery.store.model.ModelStore; | 27 | import tools.refinery.store.model.ModelStore; |
27 | import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; | 28 | import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; |
28 | import tools.refinery.store.reasoning.ReasoningAdapter; | 29 | import tools.refinery.store.reasoning.ReasoningAdapter; |
29 | import tools.refinery.store.reasoning.ReasoningStoreAdapter; | 30 | import tools.refinery.store.reasoning.ReasoningStoreAdapter; |
30 | import tools.refinery.store.reasoning.literal.Concreteness; | 31 | import tools.refinery.store.reasoning.literal.Concreteness; |
32 | import tools.refinery.store.reasoning.refinement.RefinementResult; | ||
31 | import tools.refinery.store.reasoning.representation.PartialRelation; | 33 | import tools.refinery.store.reasoning.representation.PartialRelation; |
34 | import tools.refinery.store.reasoning.scope.ScopePropagatorAdapter; | ||
32 | import tools.refinery.store.reasoning.translator.TranslationException; | 35 | import tools.refinery.store.reasoning.translator.TranslationException; |
33 | import tools.refinery.store.representation.TruthValue; | 36 | import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator; |
34 | import tools.refinery.store.tuple.Tuple; | 37 | import tools.refinery.store.tuple.Tuple; |
35 | import tools.refinery.viatra.runtime.CancellationToken; | 38 | import 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 | */ | ||
6 | package tools.refinery.store.reasoning.scope.internal; | ||
7 | |||
8 | import tools.refinery.store.query.view.TuplePreservingView; | ||
9 | import tools.refinery.store.representation.Symbol; | ||
10 | import tools.refinery.store.representation.cardinality.CardinalityInterval; | ||
11 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; | ||
12 | import tools.refinery.store.tuple.Tuple; | ||
13 | |||
14 | class 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 | ||
23 | import java.util.*; | 23 | import java.util.*; |
24 | 24 | ||
25 | import static tools.refinery.store.query.literal.Literals.not; | ||
26 | import static tools.refinery.store.reasoning.ReasoningAdapter.EQUALS_SYMBOL; | ||
27 | import static tools.refinery.store.reasoning.ReasoningAdapter.EXISTS_SYMBOL; | ||
28 | import static tools.refinery.store.reasoning.literal.PartialLiterals.may; | ||
29 | import static tools.refinery.store.reasoning.literal.PartialLiterals.must; | ||
30 | |||
31 | public class ScopePropagatorBuilderImpl extends AbstractModelAdapterBuilder<ScopePropagatorStoreAdapter> | 25 | public 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 | } |