@@ -21,11 +21,13 @@ use crate::optimisation::Direction;
21
21
use crate :: optimisation:: OptimisationResult ;
22
22
use crate :: optimisation:: Optimiser ;
23
23
use crate :: result:: SatisfactionResult ;
24
+ use crate :: result:: SatisfactionUnderAssumptionsResult ;
24
25
use crate :: result:: Solution ;
25
26
use crate :: variables:: BoolExpression ;
26
27
use crate :: variables:: BoolVariable ;
27
28
use crate :: variables:: IntExpression ;
28
29
use crate :: variables:: IntVariable ;
30
+ use crate :: variables:: Predicate ;
29
31
use crate :: variables:: VariableMap ;
30
32
31
33
#[ pyclass]
@@ -36,15 +38,6 @@ pub struct Model {
36
38
constraints : Vec < ModelConstraint > ,
37
39
}
38
40
39
- #[ pyclass( eq, eq_int) ]
40
- #[ derive( Clone , Copy , PartialEq , Eq ) ]
41
- pub enum Comparator {
42
- NotEqual ,
43
- Equal ,
44
- LessThanOrEqual ,
45
- GreaterThanOrEqual ,
46
- }
47
-
48
41
#[ pymethods]
49
42
impl Model {
50
43
#[ new]
@@ -121,23 +114,13 @@ impl Model {
121
114
}
122
115
}
123
116
124
- #[ pyo3( signature = ( integer, comparator, value, name=None ) ) ]
125
- fn predicate_as_boolean (
126
- & mut self ,
127
- integer : IntExpression ,
128
- comparator : Comparator ,
129
- value : i32 ,
130
- name : Option < & str > ,
131
- ) -> BoolExpression {
117
+ #[ pyo3( signature = ( predicate, name=None ) ) ]
118
+ fn predicate_as_boolean ( & mut self , predicate : Predicate , name : Option < & str > ) -> BoolExpression {
132
119
self . boolean_variables
133
120
. push ( ModelBoolVar {
134
121
name : name. map ( |n| n. to_owned ( ) ) ,
135
122
integer_equivalent : None ,
136
- predicate : Some ( Predicate {
137
- integer,
138
- comparator,
139
- value,
140
- } ) ,
123
+ predicate : Some ( predicate) ,
141
124
} )
142
125
. into ( )
143
126
}
@@ -191,6 +174,70 @@ impl Model {
191
174
}
192
175
}
193
176
177
+ #[ pyo3( signature = ( assumptions) ) ]
178
+ fn satisfy_under_assumptions (
179
+ & self ,
180
+ assumptions : Vec < Predicate > ,
181
+ ) -> SatisfactionUnderAssumptionsResult {
182
+ let solver_setup = self . create_solver ( None ) ;
183
+
184
+ let Ok ( ( mut solver, variable_map) ) = solver_setup else {
185
+ return SatisfactionUnderAssumptionsResult :: Unsatisfiable ( ) ;
186
+ } ;
187
+
188
+ let mut brancher = solver. default_brancher ( ) ;
189
+
190
+ let solver_assumptions = assumptions
191
+ . iter ( )
192
+ . map ( |pred| pred. to_solver_predicate ( & variable_map) )
193
+ . collect :: < Vec < _ > > ( ) ;
194
+
195
+ // Maarten: I do not understand why it is necessary, but we have to create a local variable
196
+ // here that is the result of the `match` statement. Otherwise the compiler
197
+ // complains that `solver` and `brancher` potentially do not live long enough.
198
+ //
199
+ // Ideally this would not be necessary, but perhaps it is unavoidable with the setup we
200
+ // currently have. Either way, we take the suggestion by the compiler.
201
+ let result = match solver. satisfy_under_assumptions ( & mut brancher, & mut Indefinite , & solver_assumptions) {
202
+ pumpkin_solver:: results:: SatisfactionResultUnderAssumptions :: Satisfiable ( solution) => {
203
+ SatisfactionUnderAssumptionsResult :: Satisfiable ( Solution {
204
+ solver_solution : solution,
205
+ variable_map,
206
+ } )
207
+ }
208
+ pumpkin_solver:: results:: SatisfactionResultUnderAssumptions :: UnsatisfiableUnderAssumptions ( mut result) => {
209
+ // Maarten: For now we assume that the core _must_ consist of the predicates that
210
+ // were the input to the solve call. In general this is not the case, e.g. when
211
+ // the assumptions can be semantically minized (the assumptions [y <= 1],
212
+ // [y >= 0] and [y != 0] will be compressed to [y == 1] which would end up in
213
+ // the core).
214
+ //
215
+ // In the future, perhaps we should make the distinction between predicates and
216
+ // literals in the python wrapper as well. For now, this is the simplest way
217
+ // forward. I expect that the situation above almost never happens in practice.
218
+ let core = result
219
+ . extract_core ( )
220
+ . iter ( )
221
+ . map ( |predicate| assumptions
222
+ . iter ( )
223
+ . find ( |pred| pred. to_solver_predicate ( & variable_map) == * predicate)
224
+ . copied ( )
225
+ . expect ( "predicates in core must be part of the assumptions" ) )
226
+ . collect ( ) ;
227
+
228
+ SatisfactionUnderAssumptionsResult :: UnsatisfiableUnderAssumptions ( core)
229
+ }
230
+ pumpkin_solver:: results:: SatisfactionResultUnderAssumptions :: Unsatisfiable => {
231
+ SatisfactionUnderAssumptionsResult :: Unsatisfiable ( )
232
+ }
233
+ pumpkin_solver:: results:: SatisfactionResultUnderAssumptions :: Unknown => {
234
+ SatisfactionUnderAssumptionsResult :: Unknown ( )
235
+ }
236
+ } ;
237
+
238
+ result
239
+ }
240
+
194
241
#[ pyo3( signature = ( objective, optimiser=Optimiser :: LinearSatUnsat , direction=Direction :: Minimise , proof=None ) ) ]
195
242
fn optimise (
196
243
& self ,
@@ -411,26 +458,3 @@ impl ModelBoolVar {
411
458
Ok ( literal)
412
459
}
413
460
}
414
-
415
- struct Predicate {
416
- integer : IntExpression ,
417
- comparator : Comparator ,
418
- value : i32 ,
419
- }
420
-
421
- impl Predicate {
422
- /// Convert the predicate in the model domain to a predicate in the solver domain.
423
- fn to_solver_predicate (
424
- & self ,
425
- variable_map : & VariableMap ,
426
- ) -> pumpkin_solver:: predicates:: Predicate {
427
- let affine_view = self . integer . to_affine_view ( variable_map) ;
428
-
429
- match self . comparator {
430
- Comparator :: NotEqual => predicate ! [ affine_view != self . value] ,
431
- Comparator :: Equal => predicate ! [ affine_view == self . value] ,
432
- Comparator :: LessThanOrEqual => predicate ! [ affine_view <= self . value] ,
433
- Comparator :: GreaterThanOrEqual => predicate ! [ affine_view >= self . value] ,
434
- }
435
- }
436
- }
0 commit comments