diff options
Diffstat (limited to 'Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronScopePropagator.xtend')
-rw-r--r-- | Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronScopePropagator.xtend | 355 |
1 files changed, 289 insertions, 66 deletions
diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronScopePropagator.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronScopePropagator.xtend index cebd89da..4f0c8f20 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronScopePropagator.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronScopePropagator.xtend | |||
@@ -2,90 +2,60 @@ package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality | |||
2 | 2 | ||
3 | import com.google.common.collect.ImmutableList | 3 | import com.google.common.collect.ImmutableList |
4 | import com.google.common.collect.ImmutableMap | 4 | import com.google.common.collect.ImmutableMap |
5 | import com.google.common.collect.Maps | ||
5 | import hu.bme.mit.inf.dslreasoner.logic.model.logiclanguage.Type | 6 | import hu.bme.mit.inf.dslreasoner.logic.model.logiclanguage.Type |
7 | import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.patterns.UnifinishedMultiplicityQueries | ||
6 | import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialComplexTypeInterpretation | 8 | import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialComplexTypeInterpretation |
7 | import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialInterpretation | 9 | import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialInterpretation |
8 | import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialPrimitiveInterpretation | 10 | import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialPrimitiveInterpretation |
9 | import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.Scope | 11 | import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.Scope |
10 | import java.util.ArrayDeque | 12 | import java.util.ArrayDeque |
13 | import java.util.ArrayList | ||
11 | import java.util.HashMap | 14 | import java.util.HashMap |
12 | import java.util.HashSet | 15 | import java.util.HashSet |
16 | import java.util.List | ||
13 | import java.util.Map | 17 | import java.util.Map |
14 | import java.util.Set | 18 | import java.util.Set |
19 | import javax.naming.OperationNotSupportedException | ||
20 | import org.eclipse.viatra.query.runtime.api.IPatternMatch | ||
21 | import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine | ||
22 | import org.eclipse.viatra.query.runtime.api.ViatraQueryMatcher | ||
23 | import org.eclipse.viatra.query.runtime.emf.EMFScope | ||
24 | import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor | ||
15 | 25 | ||
16 | class PolyhedronScopePropagator extends ScopePropagator { | 26 | class PolyhedronScopePropagator extends ScopePropagator { |
17 | val Map<Scope, LinearBoundedExpression> scopeBounds | 27 | val Map<Scope, LinearBoundedExpression> scopeBounds |
18 | val LinearConstraint topLevelBounds | 28 | val LinearBoundedExpression topLevelBounds |
29 | val Polyhedron polyhedron | ||
19 | val PolyhedronSaturationOperator operator | 30 | val PolyhedronSaturationOperator operator |
31 | List<RelationConstraintUpdater> updaters = emptyList | ||
20 | 32 | ||
21 | new(PartialInterpretation p, Set<? extends Type> possibleNewDynamicTypes, PolyhedronSolver solver) { | 33 | new(PartialInterpretation p, Set<? extends Type> possibleNewDynamicTypes, |
34 | Map<RelationMultiplicityConstraint, UnifinishedMultiplicityQueries> unfinishedMultiplicityQueries, | ||
35 | PolyhedronSolver solver, boolean propagateRelations) { | ||
22 | super(p) | 36 | super(p) |
23 | val instanceCounts = possibleNewDynamicTypes.toInvertedMap[new Dimension(name, 0, null)] | 37 | val builder = new PolyhedronBuilder(p) |
24 | val primitiveDimensions = new HashMap | 38 | builder.buildPolyhedron(possibleNewDynamicTypes) |
25 | val constraintsBuilder = ImmutableList.builder | 39 | scopeBounds = builder.scopeBounds |
26 | val scopeBoundsBuilder = ImmutableMap.builder | 40 | topLevelBounds = builder.topLevelBounds |
27 | // Dimensions for instantiable types were created according to the type analysis, | 41 | polyhedron = builder.polyhedron |
28 | // but for any possible primitive types, we create them on demand, | ||
29 | // as there is no Type directly associated with a PartialPrimitiveInterpretation. | ||
30 | // Below we will assume that each PartialTypeInterpretation has at most one Scope. | ||
31 | for (scope : p.scopes) { | ||
32 | switch (targetTypeInterpretation : scope.targetTypeInterpretation) { | ||
33 | PartialPrimitiveInterpretation: { | ||
34 | val dimension = primitiveDimensions.computeIfAbsent(targetTypeInterpretation) [ interpretation | | ||
35 | new Dimension(interpretation.eClass.name, 0, null) | ||
36 | ] | ||
37 | scopeBoundsBuilder.put(scope, dimension) | ||
38 | } | ||
39 | PartialComplexTypeInterpretation: { | ||
40 | val complexType = targetTypeInterpretation.interpretationOf | ||
41 | val dimensions = findSubtypeDimensions(complexType, instanceCounts) | ||
42 | switch (dimensions.size) { | ||
43 | case 0: | ||
44 | if (scope.minNewElements > 0) { | ||
45 | throw new IllegalArgumentException("Found scope for " + complexType.name + | ||
46 | ", but the type cannot be instantiated") | ||
47 | } | ||
48 | case 1: | ||
49 | scopeBoundsBuilder.put(scope, dimensions.head) | ||
50 | default: { | ||
51 | val constraint = new LinearConstraint(dimensions.toInvertedMap[1], null, null) | ||
52 | constraintsBuilder.add(constraint) | ||
53 | scopeBoundsBuilder.put(scope, constraint) | ||
54 | } | ||
55 | } | ||
56 | } | ||
57 | default: | ||
58 | throw new IllegalArgumentException("Unknown PartialTypeInterpretation: " + targetTypeInterpretation) | ||
59 | } | ||
60 | } | ||
61 | val allDimensions = ImmutableList.builder.addAll(instanceCounts.values).addAll(primitiveDimensions.values).build | ||
62 | scopeBounds = scopeBoundsBuilder.build | ||
63 | topLevelBounds = new LinearConstraint(allDimensions.toInvertedMap[1], null, null) | ||
64 | constraintsBuilder.add(topLevelBounds) | ||
65 | val expressionsToSaturate = ImmutableList.builder.addAll(scopeBounds.values).add(topLevelBounds).build | ||
66 | val polyhedron = new Polyhedron(allDimensions, constraintsBuilder.build, expressionsToSaturate) | ||
67 | operator = solver.createSaturationOperator(polyhedron) | 42 | operator = solver.createSaturationOperator(polyhedron) |
68 | } | 43 | if (propagateRelations) { |
69 | 44 | propagateAllScopeConstraints() | |
70 | private def findSubtypeDimensions(Type type, Map<Type, Dimension> instanceCounts) { | 45 | val maximumNumberOfNewNodes = topLevelBounds.upperBound |
71 | val subtypes = new HashSet | 46 | if (maximumNumberOfNewNodes === null) { |
72 | val dimensions = new HashSet | 47 | throw new IllegalStateException("Could not determine maximum number of new nodes, it may be unbounded") |
73 | val stack = new ArrayDeque | 48 | } |
74 | stack.addLast(type) | 49 | if (maximumNumberOfNewNodes <= 0) { |
75 | while (!stack.empty) { | 50 | throw new IllegalStateException("Maximum number of new nodes is negative") |
76 | val subtype = stack.removeLast | ||
77 | if (subtypes.add(subtype)) { | ||
78 | val dimension = instanceCounts.get(subtype) | ||
79 | if (dimension !== null) { | ||
80 | dimensions.add(dimension) | ||
81 | } | ||
82 | stack.addAll(subtype.subtypes) | ||
83 | } | 51 | } |
52 | builder.buildMultiplicityConstraints(unfinishedMultiplicityQueries, maximumNumberOfNewNodes) | ||
53 | updaters = builder.updaters | ||
84 | } | 54 | } |
85 | dimensions | ||
86 | } | 55 | } |
87 | 56 | ||
88 | override void propagateAllScopeConstraints() { | 57 | override void propagateAllScopeConstraints() { |
58 | resetBounds() | ||
89 | populatePolyhedronFromScope() | 59 | populatePolyhedronFromScope() |
90 | val result = operator.saturate() | 60 | val result = operator.saturate() |
91 | if (result == PolyhedronSaturationResult.EMPTY) { | 61 | if (result == PolyhedronSaturationResult.EMPTY) { |
@@ -96,21 +66,36 @@ class PolyhedronScopePropagator extends ScopePropagator { | |||
96 | super.propagateAllScopeConstraints() | 66 | super.propagateAllScopeConstraints() |
97 | } | 67 | } |
98 | } | 68 | } |
69 | // println(polyhedron) | ||
70 | } | ||
71 | |||
72 | def resetBounds() { | ||
73 | for (dimension : polyhedron.dimensions) { | ||
74 | dimension.lowerBound = 0 | ||
75 | dimension.upperBound = null | ||
76 | } | ||
77 | for (constraint : polyhedron.constraints) { | ||
78 | constraint.lowerBound = null | ||
79 | constraint.upperBound = null | ||
80 | } | ||
99 | } | 81 | } |
100 | 82 | ||
101 | private def populatePolyhedronFromScope() { | 83 | private def populatePolyhedronFromScope() { |
102 | topLevelBounds.lowerBound = partialInterpretation.minNewElements | 84 | topLevelBounds.tightenLowerBound(partialInterpretation.minNewElements) |
103 | if (partialInterpretation.maxNewElements >= 0) { | 85 | if (partialInterpretation.maxNewElements >= 0) { |
104 | topLevelBounds.upperBound = partialInterpretation.maxNewElements | 86 | topLevelBounds.tightenUpperBound(partialInterpretation.maxNewElements) |
105 | } | 87 | } |
106 | for (pair : scopeBounds.entrySet) { | 88 | for (pair : scopeBounds.entrySet) { |
107 | val scope = pair.key | 89 | val scope = pair.key |
108 | val bounds = pair.value | 90 | val bounds = pair.value |
109 | bounds.lowerBound = scope.minNewElements | 91 | bounds.tightenLowerBound(scope.minNewElements) |
110 | if (scope.maxNewElements >= 0) { | 92 | if (scope.maxNewElements >= 0) { |
111 | bounds.upperBound = scope.maxNewElements | 93 | bounds.tightenUpperBound(scope.maxNewElements) |
112 | } | 94 | } |
113 | } | 95 | } |
96 | for (updater : updaters) { | ||
97 | updater.update(partialInterpretation) | ||
98 | } | ||
114 | } | 99 | } |
115 | 100 | ||
116 | private def populateScopesFromPolyhedron() { | 101 | private def populateScopesFromPolyhedron() { |
@@ -151,4 +136,242 @@ class PolyhedronScopePropagator extends ScopePropagator { | |||
151 | throw new IllegalArgumentException("Infinite upper bound: " + bounds) | 136 | throw new IllegalArgumentException("Infinite upper bound: " + bounds) |
152 | } | 137 | } |
153 | } | 138 | } |
139 | |||
140 | private static def <T extends IPatternMatch> getCalculatedMultiplicity(ViatraQueryMatcher<T> matcher, | ||
141 | PartialInterpretation p) { | ||
142 | val match = matcher.newEmptyMatch | ||
143 | match.set(0, p.problem) | ||
144 | match.set(1, p) | ||
145 | val iterator = matcher.streamAllMatches(match).iterator | ||
146 | if (!iterator.hasNext) { | ||
147 | return null | ||
148 | } | ||
149 | val value = iterator.next.get(2) as Integer | ||
150 | if (iterator.hasNext) { | ||
151 | throw new IllegalArgumentException("Multiplicity calculation query has more than one match") | ||
152 | } | ||
153 | value | ||
154 | } | ||
155 | |||
156 | @FinalFieldsConstructor | ||
157 | private static class PolyhedronBuilder { | ||
158 | static val INFINITY_SCALE = 10 | ||
159 | |||
160 | val PartialInterpretation p | ||
161 | |||
162 | Map<Type, Dimension> instanceCounts | ||
163 | Map<Type, Map<Dimension, Integer>> subtypeDimensions | ||
164 | Map<Map<Dimension, Integer>, LinearBoundedExpression> expressionsCache | ||
165 | Map<Type, LinearBoundedExpression> typeBounds | ||
166 | int infinity | ||
167 | ViatraQueryEngine queryEngine | ||
168 | ImmutableList.Builder<RelationConstraintUpdater> updatersBuilder | ||
169 | |||
170 | Map<Scope, LinearBoundedExpression> scopeBounds | ||
171 | LinearBoundedExpression topLevelBounds | ||
172 | Polyhedron polyhedron | ||
173 | List<RelationConstraintUpdater> updaters | ||
174 | |||
175 | def buildPolyhedron(Set<? extends Type> possibleNewDynamicTypes) { | ||
176 | instanceCounts = possibleNewDynamicTypes.toInvertedMap[new Dimension(name, 0, null)] | ||
177 | val types = p.problem.types | ||
178 | expressionsCache = Maps.newHashMapWithExpectedSize(types.size) | ||
179 | subtypeDimensions = types.toInvertedMap[findSubtypeDimensions.toInvertedMap[1]] | ||
180 | typeBounds = ImmutableMap.copyOf(subtypeDimensions.mapValues[toExpression]) | ||
181 | scopeBounds = buildScopeBounds | ||
182 | topLevelBounds = instanceCounts.values.toInvertedMap[1].toExpression | ||
183 | val dimensions = ImmutableList.copyOf(instanceCounts.values) | ||
184 | val expressionsToSaturate = ImmutableList.copyOf(scopeBounds.values) | ||
185 | polyhedron = new Polyhedron(dimensions, new ArrayList, expressionsToSaturate) | ||
186 | addCachedConstraintsToPolyhedron() | ||
187 | } | ||
188 | |||
189 | def buildMultiplicityConstraints( | ||
190 | Map<RelationMultiplicityConstraint, UnifinishedMultiplicityQueries> constraints, | ||
191 | int maximumNuberOfNewNodes) { | ||
192 | infinity = maximumNuberOfNewNodes * INFINITY_SCALE | ||
193 | queryEngine = ViatraQueryEngine.on(new EMFScope(p)) | ||
194 | updatersBuilder = ImmutableList.builder | ||
195 | for (pair : constraints.entrySet.filter[key.containment].groupBy[key.targetType].entrySet) { | ||
196 | buildContainmentConstraints(pair.key, pair.value) | ||
197 | } | ||
198 | for (pair : constraints.entrySet) { | ||
199 | val constraint = pair.key | ||
200 | if (!constraint.containment) { | ||
201 | buildNonContainmentConstraints(constraint, pair.value) | ||
202 | } | ||
203 | } | ||
204 | updaters = updatersBuilder.build | ||
205 | addCachedConstraintsToPolyhedron() | ||
206 | } | ||
207 | |||
208 | private def addCachedConstraintsToPolyhedron() { | ||
209 | val constraints = new HashSet | ||
210 | constraints.addAll(expressionsCache.values.filter(LinearConstraint)) | ||
211 | constraints.removeAll(polyhedron.constraints) | ||
212 | polyhedron.constraints.addAll(constraints) | ||
213 | } | ||
214 | |||
215 | private def buildContainmentConstraints(Type containedType, | ||
216 | List<Map.Entry<RelationMultiplicityConstraint, UnifinishedMultiplicityQueries>> constraints) { | ||
217 | val typeCoefficients = subtypeDimensions.get(containedType) | ||
218 | val orphansLowerBoundCoefficients = new HashMap(typeCoefficients) | ||
219 | val orphansUpperBoundCoefficients = new HashMap(typeCoefficients) | ||
220 | val unfinishedMultiplicitiesMatchersBuilder = ImmutableList.builder | ||
221 | val remainingContentsQueriesBuilder = ImmutableList.builder | ||
222 | for (pair : constraints) { | ||
223 | val constraint = pair.key | ||
224 | val containerCoefficients = subtypeDimensions.get(constraint.sourceType) | ||
225 | if (constraint.isUpperBoundFinite) { | ||
226 | orphansLowerBoundCoefficients.addCoefficients(-constraint.upperBound, containerCoefficients) | ||
227 | } else { | ||
228 | orphansLowerBoundCoefficients.addCoefficients(-infinity, containerCoefficients) | ||
229 | } | ||
230 | orphansUpperBoundCoefficients.addCoefficients(-constraint.lowerBound, containerCoefficients) | ||
231 | val queries = pair.value | ||
232 | if (constraint.constrainsUnfinished) { | ||
233 | if (queries.unfinishedMultiplicityQuery === null) { | ||
234 | throw new IllegalArgumentException( | ||
235 | "Containment constraints need unfinished multiplicity queries") | ||
236 | } | ||
237 | unfinishedMultiplicitiesMatchersBuilder.add( | ||
238 | queries.unfinishedMultiplicityQuery.getMatcher(queryEngine)) | ||
239 | } | ||
240 | if (queries.remainingContentsQuery === null) { | ||
241 | throw new IllegalArgumentException("Containment constraints need remaining contents queries") | ||
242 | } | ||
243 | remainingContentsQueriesBuilder.add(queries.remainingContentsQuery.getMatcher(queryEngine)) | ||
244 | } | ||
245 | val orphanLowerBound = orphansLowerBoundCoefficients.toExpression | ||
246 | val orphanUpperBound = orphansUpperBoundCoefficients.toExpression | ||
247 | val updater = new ContainmentConstraintUpdater(containedType.name, orphanLowerBound, orphanUpperBound, | ||
248 | unfinishedMultiplicitiesMatchersBuilder.build, remainingContentsQueriesBuilder.build) | ||
249 | updatersBuilder.add(updater) | ||
250 | } | ||
251 | |||
252 | private def buildNonContainmentConstraints(RelationMultiplicityConstraint constraint, | ||
253 | UnifinishedMultiplicityQueries queries) { | ||
254 | } | ||
255 | |||
256 | private def addCoefficients(Map<Dimension, Integer> accumulator, int scale, Map<Dimension, Integer> a) { | ||
257 | for (pair : a.entrySet) { | ||
258 | val dimension = pair.key | ||
259 | val currentValue = accumulator.get(pair.key) ?: 0 | ||
260 | val newValue = currentValue + scale * pair.value | ||
261 | if (newValue == 0) { | ||
262 | accumulator.remove(dimension) | ||
263 | } else { | ||
264 | accumulator.put(dimension, newValue) | ||
265 | } | ||
266 | } | ||
267 | } | ||
268 | |||
269 | private def findSubtypeDimensions(Type type) { | ||
270 | val subtypes = new HashSet | ||
271 | val dimensions = new HashSet | ||
272 | val stack = new ArrayDeque | ||
273 | stack.addLast(type) | ||
274 | while (!stack.empty) { | ||
275 | val subtype = stack.removeLast | ||
276 | if (subtypes.add(subtype)) { | ||
277 | val dimension = instanceCounts.get(subtype) | ||
278 | if (dimension !== null) { | ||
279 | dimensions.add(dimension) | ||
280 | } | ||
281 | stack.addAll(subtype.subtypes) | ||
282 | } | ||
283 | } | ||
284 | dimensions | ||
285 | } | ||
286 | |||
287 | private def toExpression(Map<Dimension, Integer> coefficients) { | ||
288 | expressionsCache.computeIfAbsent(coefficients) [ c | | ||
289 | if (c.size == 1 && c.entrySet.head.value == 1) { | ||
290 | c.entrySet.head.key | ||
291 | } else { | ||
292 | new LinearConstraint(c, null, null) | ||
293 | } | ||
294 | ] | ||
295 | } | ||
296 | |||
297 | private def buildScopeBounds() { | ||
298 | val scopeBoundsBuilder = ImmutableMap.builder | ||
299 | for (scope : p.scopes) { | ||
300 | switch (targetTypeInterpretation : scope.targetTypeInterpretation) { | ||
301 | PartialPrimitiveInterpretation: | ||
302 | throw new OperationNotSupportedException("Primitive type scopes are not yet implemented") | ||
303 | PartialComplexTypeInterpretation: { | ||
304 | val complexType = targetTypeInterpretation.interpretationOf | ||
305 | val typeBound = typeBounds.get(complexType) | ||
306 | if (typeBound === null) { | ||
307 | if (scope.minNewElements > 0) { | ||
308 | throw new IllegalArgumentException("Found scope for " + complexType.name + | ||
309 | ", but the type cannot be instantiated") | ||
310 | } | ||
311 | } else { | ||
312 | scopeBoundsBuilder.put(scope, typeBound) | ||
313 | } | ||
314 | } | ||
315 | default: | ||
316 | throw new IllegalArgumentException("Unknown PartialTypeInterpretation: " + | ||
317 | targetTypeInterpretation) | ||
318 | } | ||
319 | } | ||
320 | scopeBoundsBuilder.build | ||
321 | } | ||
322 | } | ||
323 | |||
324 | private static interface RelationConstraintUpdater { | ||
325 | def void update(PartialInterpretation p) | ||
326 | } | ||
327 | |||
328 | @FinalFieldsConstructor | ||
329 | static class ContainmentConstraintUpdater implements RelationConstraintUpdater { | ||
330 | val String name | ||
331 | val LinearBoundedExpression orphansLowerBound | ||
332 | val LinearBoundedExpression orphansUpperBound | ||
333 | val List<ViatraQueryMatcher<? extends IPatternMatch>> unfinishedMultiplicitiesMatchers | ||
334 | val List<ViatraQueryMatcher<? extends IPatternMatch>> remainingContentsQueries | ||
335 | |||
336 | override update(PartialInterpretation p) { | ||
337 | tightenLowerBound(p) | ||
338 | tightenUpperBound(p) | ||
339 | } | ||
340 | |||
341 | private def tightenLowerBound(PartialInterpretation p) { | ||
342 | var int sum = 0 | ||
343 | for (matcher : remainingContentsQueries) { | ||
344 | val value = matcher.getCalculatedMultiplicity(p) | ||
345 | if (value === null) { | ||
346 | throw new IllegalArgumentException("Remaining contents count is missing for " + name) | ||
347 | } | ||
348 | if (value == -1) { | ||
349 | // Infinite upper bound, no need to tighten. | ||
350 | return | ||
351 | } | ||
352 | sum += value | ||
353 | } | ||
354 | orphansLowerBound.tightenUpperBound(sum) | ||
355 | } | ||
356 | |||
357 | private def tightenUpperBound(PartialInterpretation p) { | ||
358 | var int sum = 0 | ||
359 | for (matcher : unfinishedMultiplicitiesMatchers) { | ||
360 | val value = matcher.getCalculatedMultiplicity(p) | ||
361 | if (value === null) { | ||
362 | throw new IllegalArgumentException("Unfinished multiplicity is missing for " + name) | ||
363 | } | ||
364 | sum += value | ||
365 | } | ||
366 | orphansUpperBound.tightenLowerBound(sum) | ||
367 | } | ||
368 | } | ||
369 | |||
370 | @FinalFieldsConstructor | ||
371 | static class ContainmentRootConstraintUpdater implements RelationConstraintUpdater { | ||
372 | |||
373 | override update(PartialInterpretation p) { | ||
374 | throw new UnsupportedOperationException("TODO: auto-generated method stub") | ||
375 | } | ||
376 | } | ||
154 | } | 377 | } |