aboutsummaryrefslogtreecommitdiffstats
path: root/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/CbcPolyhedronSolver.xtend
blob: 7753e68e026d741fae5afab9912db56e39cfa793 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality

import com.google.common.collect.ImmutableMap
import hu.bme.mit.inf.dslreasoner.ilp.cbc.CbcResult
import hu.bme.mit.inf.dslreasoner.ilp.cbc.CbcSolver
import java.util.List
import java.util.Map
import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor

@FinalFieldsConstructor
class CbcPolyhedronSolver implements PolyhedronSolver {
	val double timeoutSeconds
	val boolean silent

	new() {
		this(10, true)
	}

	override createSaturationOperator(Polyhedron polyhedron) {
		new CbcSaturationOperator(polyhedron, timeoutSeconds, silent)
	}
}

class CbcSaturationOperator extends AbstractPolyhedronSaturationOperator {
	val double timeoutSeconds
	val boolean silent
	val double[] columnLowerBounds
	val double[] columnUpperBounds
	val double[] objective
	val Map<Dimension, Integer> dimensionsToIndicesMap

	new(Polyhedron polyhedron, double timeoutSeconds, boolean silent) {
		super(polyhedron)
		this.timeoutSeconds = timeoutSeconds
		this.silent = silent
		val numDimensions = polyhedron.dimensions.size
		columnLowerBounds = newDoubleArrayOfSize(numDimensions)
		columnUpperBounds = newDoubleArrayOfSize(numDimensions)
		objective = newDoubleArrayOfSize(numDimensions)
		dimensionsToIndicesMap = ImmutableMap.copyOf(polyhedron.dimensions.indexed.toMap([value], [key]))
	}

	override doSaturate() {
		val numDimensions = polyhedron.dimensions.size
		for (var int j = 0; j < numDimensions; j++) {
			val dimension = polyhedron.dimensions.get(j)
			columnLowerBounds.set(j, dimension.lowerBound.toDouble(Double.NEGATIVE_INFINITY))
			columnUpperBounds.set(j, dimension.upperBound.toDouble(Double.POSITIVE_INFINITY))
		}
		val constraints = nonTrivialConstraints
		val numConstraints = constraints.size
		val rowStarts = newIntArrayOfSize(numConstraints + 1)
		val rowLowerBounds = newDoubleArrayOfSize(numConstraints)
		val rowUpperBounds = newDoubleArrayOfSize(numConstraints)
		val numEntries = constraints.map[coefficients.size].reduce[a, b|a + b] ?: 0
		rowStarts.set(numConstraints, numEntries)
		val columnIndices = newIntArrayOfSize(numEntries)
		val entries = newDoubleArrayOfSize(numEntries)
		var int index = 0
		for (var int i = 0; i < numConstraints; i++) {
			rowStarts.set(i, index)
			val constraint = constraints.get(i)
			rowLowerBounds.set(i, constraint.lowerBound.toDouble(Double.NEGATIVE_INFINITY))
			rowUpperBounds.set(i, constraint.upperBound.toDouble(Double.POSITIVE_INFINITY))
			if (!dimensionsToIndicesMap.keySet.containsAll(constraint.coefficients.keySet)) {
				throw new IllegalArgumentException("Constrains has unknown dimensions")
			}
			for (var int j = 0; j < numDimensions; j++) {
				val dimension = polyhedron.dimensions.get(j)
				val coefficient = constraint.coefficients.get(dimension)
				if (coefficient !== null && coefficient != 0) {
					columnIndices.set(index, j)
					entries.set(index, coefficient)
					index++
				}
			}
		}
		if (index != numEntries) {
			throw new AssertionError("Last entry does not equal the number of entries in the constraint matrix")
		}
		for (expressionToSaturate : polyhedron.expressionsToSaturate) {
			for (var int j = 0; j < numDimensions; j++) {
				objective.set(j, 0)
			}
			switch (expressionToSaturate) {
				Dimension: {
					val j = getIndex(expressionToSaturate)
					objective.set(j, 1)
				}
				LinearConstraint: {
					for (pair : expressionToSaturate.coefficients.entrySet) {
						val j = getIndex(pair.key)
						objective.set(j, pair.value)
					}
				}
				default:
					throw new IllegalArgumentException("Unknown expression: " + expressionToSaturate)
			}
			val minimizationResult = CbcSolver.solve(columnLowerBounds, columnUpperBounds, rowStarts, columnIndices,
				entries, rowLowerBounds, rowUpperBounds, objective, timeoutSeconds, silent)
			switch (minimizationResult) {
				CbcResult.SolutionBounded: {
					val value = Math.floor(minimizationResult.value)
					expressionToSaturate.lowerBound = value as int
					setBound(expressionToSaturate, constraints, value, columnLowerBounds, rowLowerBounds)
				}
				case CbcResult.SOLUTION_UNBOUNDED: {
					expressionToSaturate.lowerBound = null
					setBound(expressionToSaturate, constraints, Double.NEGATIVE_INFINITY, columnLowerBounds,
						rowLowerBounds)
				}
				case CbcResult.UNSAT:
					return PolyhedronSaturationResult.EMPTY
				case CbcResult.ABANDONED,
				case CbcResult.TIMEOUT:
					return PolyhedronSaturationResult.UNKNOWN
				default:
					throw new RuntimeException("Unknown CbcResult: " + minimizationResult)
			}
			for (var int j = 0; j < numDimensions; j++) {
				val objectiveCoefficient = objective.get(j)
				objective.set(j, -objectiveCoefficient)
			}
			val maximizationResult = CbcSolver.solve(columnLowerBounds, columnUpperBounds, rowStarts, columnIndices,
				entries, rowLowerBounds, rowUpperBounds, objective, timeoutSeconds, silent)
			switch (maximizationResult) {
				CbcResult.SolutionBounded: {
					val value = Math.ceil(-maximizationResult.value)
					expressionToSaturate.upperBound = value as int
					setBound(expressionToSaturate, constraints, value, columnUpperBounds, rowUpperBounds)
				}
				case CbcResult.SOLUTION_UNBOUNDED: {
					expressionToSaturate.upperBound = null
					setBound(expressionToSaturate, constraints, Double.POSITIVE_INFINITY, columnUpperBounds,
						rowUpperBounds)
				}
				case CbcResult.UNSAT:
					throw new RuntimeException("Minimization was SAT, but maximization is UNSAT")
				case CbcResult.ABANDONED,
				case CbcResult.TIMEOUT:
					return PolyhedronSaturationResult.UNKNOWN
				default:
					throw new RuntimeException("Unknown CbcResult: " + maximizationResult)
			}
		}
		PolyhedronSaturationResult.SATURATED
	}

	private def toDouble(Integer nullableInt, double defaultValue) {
		if (nullableInt === null) {
			defaultValue
		} else {
			nullableInt.doubleValue
		}
	}

	private def int getIndex(Dimension dimension) {
		val index = dimensionsToIndicesMap.get(dimension)
		if (index === null) {
			throw new IllegalArgumentException("Unknown dimension: " + dimension)
		}
		index
	}

	private def void setBound(LinearBoundedExpression expression, List<LinearConstraint> constraints, double bound,
		double[] columnBounds, double[] rowBounds) {
		switch (expression) {
			Dimension: {
				val j = getIndex(expression)
				columnBounds.set(j, bound)
			}
			LinearConstraint: {
				val i = constraints.indexOf(expression)
				if (i >= 0) {
					rowBounds.set(i, bound)
				}
			}
		}
	}
}