diff options
Diffstat (limited to 'Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf')
5 files changed, 129 insertions, 20 deletions
diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/ModelGenerationMethodProvider.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/ModelGenerationMethodProvider.xtend index be11ed78..ca09ae00 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/ModelGenerationMethodProvider.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/ModelGenerationMethodProvider.xtend | |||
@@ -77,7 +77,7 @@ class ModelGenerationMethodProvider { | |||
77 | objectRefinementRules = refinementRuleProvider.createObjectRefinementRules(logicProblem, emptySolution, queries,scopePropagator,nameNewElements,statistics) | 77 | objectRefinementRules = refinementRuleProvider.createObjectRefinementRules(logicProblem, emptySolution, queries,scopePropagator,nameNewElements,statistics) |
78 | val relationRefinementRules = refinementRuleProvider.createRelationRefinementRules(queries,statistics) | 78 | val relationRefinementRules = refinementRuleProvider.createRelationRefinementRules(queries,statistics) |
79 | 79 | ||
80 | val unfinishedMultiplicities = goalConstraintProvider.getUnfinishedMultiplicityQueries(queries) | 80 | val unfinishedMultiplicities = goalConstraintProvider.getUnfinishedMultiplicityQueries(logicProblem,queries) |
81 | val unfinishedWF = queries.getUnfinishedWFQueries.values | 81 | val unfinishedWF = queries.getUnfinishedWFQueries.values |
82 | 82 | ||
83 | val invalidWF = queries.getInvalidWFQueries.values | 83 | val invalidWF = queries.getInvalidWFQueries.values |
diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/MultiplicityGoalConstraintCalculator.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/MultiplicityGoalConstraintCalculator.xtend index e05160d0..e1358fb6 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/MultiplicityGoalConstraintCalculator.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/MultiplicityGoalConstraintCalculator.xtend | |||
@@ -10,23 +10,33 @@ class MultiplicityGoalConstraintCalculator { | |||
10 | val String targetRelationName; | 10 | val String targetRelationName; |
11 | val IQuerySpecification<?> querySpecification; | 11 | val IQuerySpecification<?> querySpecification; |
12 | var ViatraQueryMatcher<?> matcher; | 12 | var ViatraQueryMatcher<?> matcher; |
13 | val boolean containment | ||
14 | val int cost | ||
13 | 15 | ||
14 | public new(String targetRelationName, IQuerySpecification<?> querySpecification) { | 16 | public new(String targetRelationName, IQuerySpecification<?> querySpecification, boolean containment, int cost) { |
15 | this.targetRelationName = targetRelationName | 17 | this.targetRelationName = targetRelationName |
16 | this.querySpecification = querySpecification | 18 | this.querySpecification = querySpecification |
17 | this.matcher = null | 19 | this.matcher = null |
20 | this.containment = containment | ||
21 | this.cost = cost | ||
18 | } | 22 | } |
19 | 23 | ||
20 | public new(MultiplicityGoalConstraintCalculator other) { | 24 | public new(MultiplicityGoalConstraintCalculator other) { |
21 | this.targetRelationName = other.targetRelationName | 25 | this.targetRelationName = other.targetRelationName |
22 | this.querySpecification = other.querySpecification | 26 | this.querySpecification = other.querySpecification |
23 | this.matcher = null | 27 | this.matcher = null |
28 | this.containment = other.containment | ||
29 | this.cost = other.cost | ||
24 | } | 30 | } |
25 | 31 | ||
26 | def public getName() { | 32 | def public getName() { |
27 | targetRelationName | 33 | targetRelationName |
28 | } | 34 | } |
29 | 35 | ||
36 | def isContainment() { | ||
37 | return containment | ||
38 | } | ||
39 | |||
30 | def public init(Notifier notifier) { | 40 | def public init(Notifier notifier) { |
31 | val engine = ViatraQueryEngine.on(new EMFScope(notifier)) | 41 | val engine = ViatraQueryEngine.on(new EMFScope(notifier)) |
32 | matcher = querySpecification.getMatcher(engine) | 42 | matcher = querySpecification.getMatcher(engine) |
@@ -36,11 +46,14 @@ class MultiplicityGoalConstraintCalculator { | |||
36 | var res = 0 | 46 | var res = 0 |
37 | val allMatches = this.matcher.allMatches | 47 | val allMatches = this.matcher.allMatches |
38 | for(match : allMatches) { | 48 | for(match : allMatches) { |
39 | //println(targetRelationName+ " missing multiplicity: "+match.get(3)) | 49 | |
40 | val missingMultiplicity = match.get(4) as Integer | 50 | val missingMultiplicity = match.get(4) as Integer |
41 | res += missingMultiplicity | 51 | res += missingMultiplicity |
52 | if(missingMultiplicity!=0) { | ||
53 | println(targetRelationName+ " missing multiplicity: "+missingMultiplicity) | ||
54 | } | ||
42 | } | 55 | } |
43 | //println(targetRelationName+ " all missing multiplicities: "+res) | 56 | //println(targetRelationName+ " all missing multiplicities: "+res) |
44 | return res | 57 | return res*cost |
45 | } | 58 | } |
46 | } \ No newline at end of file | 59 | } \ No newline at end of file |
diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/PConstraintTransformer.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/PConstraintTransformer.xtend index 4a8da38c..a4dcefbf 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/PConstraintTransformer.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/PConstraintTransformer.xtend | |||
@@ -235,10 +235,10 @@ class PConstraintTransformer { | |||
235 | «ENDFOR» | 235 | «ENDFOR» |
236 | check( | 236 | check( |
237 | «FOR variable: e.affectedVariables SEPARATOR " || "»!«variable.valueSetted»«ENDFOR» | 237 | «FOR variable: e.affectedVariables SEPARATOR " || "»!«variable.valueSetted»«ENDFOR» |
238 | «IF variable2Type.values.filter(RealTypeReference).empty» | 238 | ««« «IF variable2Type.values.filter(RealTypeReference).empty» |
239 | || | 239 | ««« || |
240 | («expressionGenerator.translateExpression(expression,e.affectedVariables.toInvertedMap[valueVariable],variable2Type)») | 240 | ««« («expressionGenerator.translateExpression(expression,e.affectedVariables.toInvertedMap[valueVariable],variable2Type)») |
241 | «ENDIF» | 241 | ««« «ENDIF» |
242 | ); | 242 | ); |
243 | ''' | 243 | ''' |
244 | } else { // Must or Current | 244 | } else { // Must or Current |
@@ -246,9 +246,9 @@ class PConstraintTransformer { | |||
246 | «FOR variable: e.affectedVariables» | 246 | «FOR variable: e.affectedVariables» |
247 | PrimitiveElement.valueSet(«variable.canonizeName»,true); «hasValueExpression(variableMapping,variable,variable.valueVariable)» | 247 | PrimitiveElement.valueSet(«variable.canonizeName»,true); «hasValueExpression(variableMapping,variable,variable.valueVariable)» |
248 | «ENDFOR» | 248 | «ENDFOR» |
249 | «IF variable2Type.values.filter(RealTypeReference).empty» | 249 | ««« «IF variable2Type.values.filter(RealTypeReference).empty» |
250 | check(«expressionGenerator.translateExpression(expression,e.affectedVariables.toInvertedMap[valueVariable],variable2Type)»); | 250 | ««« check(«expressionGenerator.translateExpression(expression,e.affectedVariables.toInvertedMap[valueVariable],variable2Type)»); |
251 | «ENDIF» | 251 | ««« «ENDIF» |
252 | ''' | 252 | ''' |
253 | } | 253 | } |
254 | } | 254 | } |
diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/PatternProvider.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/PatternProvider.xtend index 750107f6..cfea499b 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/PatternProvider.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/PatternProvider.xtend | |||
@@ -27,7 +27,8 @@ import java.util.HashMap | |||
27 | @Data class GeneratedPatterns { | 27 | @Data class GeneratedPatterns { |
28 | public Map<Relation, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> invalidWFQueries | 28 | public Map<Relation, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> invalidWFQueries |
29 | public Map<Relation, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> unfinishedWFQueries | 29 | public Map<Relation, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> unfinishedWFQueries |
30 | public Map<Relation, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> unfinishedMulticiplicityQueries | 30 | public Map<Relation, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> unfinishedContainmentMulticiplicityQueries |
31 | public Map<Relation, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> unfinishedNonContainmentMulticiplicityQueries | ||
31 | public Map<ObjectCreationPrecondition, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> refineObjectQueries | 32 | public Map<ObjectCreationPrecondition, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> refineObjectQueries |
32 | public Map<? extends Type, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> refineTypeQueries | 33 | public Map<? extends Type, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> refineTypeQueries |
33 | public Map<Pair<RelationDeclaration, Relation>, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> refinerelationQueries | 34 | public Map<Pair<RelationDeclaration, Relation>, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> refinerelationQueries |
@@ -86,8 +87,23 @@ class PatternProvider { | |||
86 | invalidWFQueries = patternGenerator.invalidIndexer.getInvalidateByWfQueryNames(problem).mapValues[it.lookup(queries)] | 87 | invalidWFQueries = patternGenerator.invalidIndexer.getInvalidateByWfQueryNames(problem).mapValues[it.lookup(queries)] |
87 | val Map<Relation, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> | 88 | val Map<Relation, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> |
88 | unfinishedWFQueries = patternGenerator.unfinishedIndexer.getUnfinishedWFQueryNames(problem).mapValues[it.lookup(queries)] | 89 | unfinishedWFQueries = patternGenerator.unfinishedIndexer.getUnfinishedWFQueryNames(problem).mapValues[it.lookup(queries)] |
89 | val Map<Relation, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> | 90 | |
90 | unfinishedMultiplicityQueries = patternGenerator.unfinishedIndexer.getUnfinishedMultiplicityQueries(problem).mapValues[it.lookup(queries)] | 91 | val unfinishedMultiplicities = patternGenerator.unfinishedIndexer.getUnfinishedMultiplicityQueries(problem) |
92 | val unfinishedContainmentMultiplicities = new HashMap | ||
93 | val unfinishedNonContainmentMultiplicities = new HashMap | ||
94 | for(entry : unfinishedMultiplicities.entrySet) { | ||
95 | val relation = entry.key | ||
96 | val value = entry.value.lookup(queries) | ||
97 | if(problem.containmentHierarchies.head.containmentRelations.contains(relation)) { | ||
98 | unfinishedContainmentMultiplicities.put(relation,value) | ||
99 | } else { | ||
100 | unfinishedNonContainmentMultiplicities.put(relation,value) | ||
101 | } | ||
102 | } | ||
103 | // val Map<Relation, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> | ||
104 | // unfinishedMultiplicityQueries = patternGenerator.unfinishedIndexer.getUnfinishedMultiplicityQueries(problem).mapValues[it.lookup(queries)] | ||
105 | // | ||
106 | |||
91 | val Map<ObjectCreationPrecondition, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> | 107 | val Map<ObjectCreationPrecondition, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> |
92 | refineObjectsQueries = patternGenerator.typeRefinementGenerator.getRefineObjectQueryNames(problem,emptySolution,typeAnalysisResult).mapValues[it.lookup(queries)] | 108 | refineObjectsQueries = patternGenerator.typeRefinementGenerator.getRefineObjectQueryNames(problem,emptySolution,typeAnalysisResult).mapValues[it.lookup(queries)] |
93 | val Map<? extends Type, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> | 109 | val Map<? extends Type, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> |
@@ -101,7 +117,8 @@ class PatternProvider { | |||
101 | return new GeneratedPatterns( | 117 | return new GeneratedPatterns( |
102 | invalidWFQueries, | 118 | invalidWFQueries, |
103 | unfinishedWFQueries, | 119 | unfinishedWFQueries, |
104 | unfinishedMultiplicityQueries, | 120 | unfinishedContainmentMultiplicities, |
121 | unfinishedNonContainmentMultiplicities, | ||
105 | refineObjectsQueries, | 122 | refineObjectsQueries, |
106 | refineTypeQueries, | 123 | refineTypeQueries, |
107 | refineRelationQueries, | 124 | refineRelationQueries, |
diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/rules/GoalConstraintProvider.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/rules/GoalConstraintProvider.xtend index e1be2742..87f7e339 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/rules/GoalConstraintProvider.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/rules/GoalConstraintProvider.xtend | |||
@@ -1,18 +1,97 @@ | |||
1 | package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.rules | 1 | package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.rules |
2 | 2 | ||
3 | import hu.bme.mit.inf.dslreasoner.ecore2logic.ecore2logicannotations.LowerMultiplicityAssertion | ||
4 | import hu.bme.mit.inf.dslreasoner.logic.model.logiclanguage.ComplexTypeReference | ||
5 | import hu.bme.mit.inf.dslreasoner.logic.model.logiclanguage.Relation | ||
6 | import hu.bme.mit.inf.dslreasoner.logic.model.logiclanguage.Type | ||
7 | import hu.bme.mit.inf.dslreasoner.logic.model.logicproblem.LogicProblem | ||
3 | import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.MultiplicityGoalConstraintCalculator | 8 | import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.MultiplicityGoalConstraintCalculator |
4 | import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.patterns.GeneratedPatterns | 9 | import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.patterns.GeneratedPatterns |
5 | import java.util.ArrayList | 10 | import java.util.ArrayList |
11 | import java.util.HashMap | ||
12 | import java.util.LinkedList | ||
13 | import java.util.List | ||
14 | import java.util.Map | ||
15 | import org.eclipse.viatra.query.runtime.api.IPatternMatch | ||
16 | import org.eclipse.viatra.query.runtime.api.IQuerySpecification | ||
17 | import org.eclipse.viatra.query.runtime.api.ViatraQueryMatcher | ||
18 | |||
19 | import static extension hu.bme.mit.inf.dslreasoner.util.CollectionsUtil.* | ||
6 | 20 | ||
7 | class GoalConstraintProvider { | 21 | class GoalConstraintProvider { |
8 | def public getUnfinishedMultiplicityQueries(GeneratedPatterns patterns) { | 22 | val calculateObjectCost = true |
9 | val multiplicityQueries = patterns.unfinishedMulticiplicityQueries | 23 | |
10 | val res = new ArrayList(multiplicityQueries.size) | 24 | def public getUnfinishedMultiplicityQueries(LogicProblem p, GeneratedPatterns patterns) { |
11 | for(multiplicityQuery : multiplicityQueries.entrySet) { | 25 | val res = new ArrayList() |
26 | |||
27 | res.addAll(patterns.unfinishedContainmentMulticiplicityQueries,true) | ||
28 | if(calculateObjectCost) { | ||
29 | val middingObjectCost = calculateMissingObjectCost(p) | ||
30 | res.addAll(patterns.unfinishedNonContainmentMulticiplicityQueries,false) | ||
31 | } else { | ||
32 | res.addAll(patterns.unfinishedNonContainmentMulticiplicityQueries,false) | ||
33 | } | ||
34 | return res | ||
35 | } | ||
36 | |||
37 | def addAll(ArrayList<MultiplicityGoalConstraintCalculator> res, Map<Relation, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> queries, boolean containment) { | ||
38 | for(multiplicityQuery : queries.entrySet) { | ||
12 | val targetRelationName = multiplicityQuery.key.name | 39 | val targetRelationName = multiplicityQuery.key.name |
13 | val query = multiplicityQuery.value | 40 | val query = multiplicityQuery.value |
14 | res += new MultiplicityGoalConstraintCalculator(targetRelationName,query); | 41 | res += new MultiplicityGoalConstraintCalculator(targetRelationName,query,containment,1); |
42 | } | ||
43 | } | ||
44 | def addAll( | ||
45 | ArrayList<MultiplicityGoalConstraintCalculator> res, | ||
46 | Map<Relation, IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> queries, | ||
47 | boolean containment, | ||
48 | Map<Relation, Integer> cost | ||
49 | ) { | ||
50 | for(multiplicityQuery : queries.entrySet) { | ||
51 | val targetRelationName = multiplicityQuery.key.name | ||
52 | val query = multiplicityQuery.value | ||
53 | res += new MultiplicityGoalConstraintCalculator(targetRelationName,query,containment,multiplicityQuery.key.lookup(cost)) | ||
54 | } | ||
55 | } | ||
56 | |||
57 | def calculateMissingObjectCost(LogicProblem p) { | ||
58 | val containments = p.containmentHierarchies.head.containmentRelations | ||
59 | val containment2Lower = containments.toInvertedMap[containment | | ||
60 | val lower = p.annotations.filter(LowerMultiplicityAssertion).filter[it.relation === containment].head | ||
61 | if(lower !== null) { lower.lower } | ||
62 | else { 0 } | ||
63 | ] | ||
64 | val types = p.types | ||
65 | val Map<Type,List<? extends Pair<Type,Integer>>> type2NewCost = new HashMap | ||
66 | for(type:types) { | ||
67 | val allSupertypes = (#[type] + type.supertypes).toSet | ||
68 | val allOutgoingContainments = containments.filter[allSupertypes.contains((it.parameters.get(0) as ComplexTypeReference).referred)] | ||
69 | val list = new LinkedList | ||
70 | for(outgoingContainment : allOutgoingContainments) { | ||
71 | val value = containment2Lower.get(outgoingContainment) | ||
72 | if(value>0) { | ||
73 | list.add((outgoingContainment.parameters.get(1) as ComplexTypeReference).referred | ||
74 | -> value) | ||
75 | } | ||
76 | } | ||
77 | type2NewCost.put(type, list) | ||
78 | } | ||
79 | val res = new HashMap | ||
80 | for(containment : containments) { | ||
81 | val key = containment | ||
82 | val value = (containment.parameters.get(1) as ComplexTypeReference).referred.count(type2NewCost) | ||
83 | //println('''«key.name» --> «value» new''') | ||
84 | res.put(key,value) | ||
15 | } | 85 | } |
16 | return res | 86 | return res |
17 | } | 87 | } |
88 | |||
89 | private def int count(Type t, Map<Type,List<? extends Pair<Type,Integer>>> containments) { | ||
90 | val list = containments.get(t) | ||
91 | var r = 1 | ||
92 | for(element : list) { | ||
93 | r += element.value * element.key.count(containments) | ||
94 | } | ||
95 | return r | ||
96 | } | ||
18 | } \ No newline at end of file | 97 | } \ No newline at end of file |