1
//! A high-level API for interacting with constraints solvers.
2
//!
3
//! This module provides a consistent, solver-independent API for interacting with constraints
4
//! solvers. It also provides incremental solving support, and the returning of run stats from
5
//! solvers.
6
//!
7
//! -----
8
//!
9
//! - [Solver<Adaptor>] provides the API for interacting with constraints solvers.
10
//!
11
//! - The [SolverAdaptor] trait controls how solving actually occurs and handles translation
12
//! between the [Solver] type and a specific solver.
13
//!
14
//! - [adaptors] contains all implemented solver adaptors.
15
//!
16
//! - The [model_modifier] submodule defines types to help with incremental solving / changing a
17
//!   model during search. The entrypoint for incremental solving is the [Solver<A,ModelLoaded>::solve_mut]
18
//!   function.
19
//!
20
//! # Examples
21
//!
22
//! ## A Successful Minion Model
23
//!
24
//! ```rust
25
1
//! use std::sync::{Arc,Mutex};
26
//! use conjure_core::parse::get_example_model;
27
//! use conjure_core::rule_engine::resolve_rule_sets;
28
//! use conjure_core::rule_engine::rewrite_model;
29
//! use conjure_core::solver::{adaptors, Solver, SolverAdaptor};
30
//! use conjure_core::solver::states::ModelLoaded;
31
//! use conjure_core::solver::SolverFamily;
32
//!
33
//! // Define and rewrite a model for minion.
34
//! let model = get_example_model("bool-03").unwrap();
35
1
//! let rule_sets = resolve_rule_sets(SolverFamily::Minion, &vec!["Constant".to_string()]).unwrap();
36
1
//! let model = rewrite_model(&model,&rule_sets).unwrap();
37
1
//!
38
1
//!
39
1
//! // Solve using Minion.
40
1
//! let solver = Solver::new(adaptors::Minion::new());
41
1
//! let solver: Solver<adaptors::Minion,ModelLoaded> = solver.load_model(model).unwrap();
42
1
//!
43
1
//! // In this example, we will count solutions.
44
1
//! //
45
1
//! // The solver interface is designed to allow adaptors to use multiple-threads / processes if
46
1
//! // necessary. Therefore, the callback type requires all variables inside it to have a static
47
1
//! // lifetime and to implement Send (i.e. the variable can be safely shared between theads).
48
1
//! //
49
1
//! // We use Arc<Mutex<T>> to create multiple references to a threadsafe mutable
50
1
//! // variable of type T.
51
1
//! //
52
1
//! // Using the move |x| ... closure syntax, we move one of these references into the closure.
53
1
//! // Note that a normal closure borrow variables from the parent so is not
54
1
//! // thread-safe.
55
1
//!
56
1
//! let counter_ref = Arc::new(Mutex::new(0));
57
1
//! let counter_ref_2 = counter_ref.clone();
58
1
//! solver.solve(Box::new(move |_| {
59
2
//!   let mut counter = (*counter_ref_2).lock().unwrap();
60
2
//!   *counter += 1;
61
2
//!   true
62
2
//!   }));
63
2
//!
64
1
//! let mut counter = (*counter_ref).lock().unwrap();
65
1
//! assert_eq!(*counter,2);
66
1
//! ```
67
1
//!
68
//! # The Solver callback function
69
//!
70
//! The callback function given to `solve` is called whenever a solution is found by the solver.
71
//!
72
//! Its return value can be used to control how many solutions the solver finds:
73
//!
74
//! * If the callback function returns `true`, solver execution continues.
75
//! * If the callback function returns `false`, the solver is terminated.
76
//!
77

            
78
// # Implementing Solver interfaces
79
//
80
// Solver interfaces can only be implemented inside this module, due to the SolverAdaptor crate
81
// being sealed.
82
//
83
// To add support for a solver, implement the `SolverAdaptor` trait in a submodule.
84
//
85
// If incremental solving support is required, also implement a new `ModelModifier`. If this is not
86
// required, all `ModelModifier` instances required by the SolverAdaptor trait can be replaced with
87
// NotModifiable.
88
//
89
// For more details, see the docstrings for SolverAdaptor, ModelModifier, and NotModifiable.
90

            
91
#![allow(dead_code)]
92
#![allow(unused)]
93
#![allow(clippy::manual_non_exhaustive)]
94

            
95
use std::any::Any;
96
use std::cell::OnceCell;
97
use std::collections::HashMap;
98
use std::error::Error;
99
use std::fmt::{Debug, Display};
100
use std::rc::Rc;
101
use std::sync::{Arc, RwLock};
102
use std::time::Instant;
103

            
104
use schemars::JsonSchema;
105
use serde::{Deserialize, Serialize};
106
use strum_macros::{Display, EnumIter, EnumString};
107
use thiserror::Error;
108

            
109
use crate::ast::{Literal, Name};
110
use crate::context::Context;
111
use crate::stats::SolverStats;
112
use crate::Model;
113

            
114
use self::model_modifier::ModelModifier;
115
use self::states::{ExecutionSuccess, Init, ModelLoaded, SolverState};
116

            
117
pub mod adaptors;
118
pub mod model_modifier;
119

            
120
#[doc(hidden)]
121
mod private;
122

            
123
pub mod states;
124

            
125
#[derive(
126
    Debug,
127
    EnumString,
128
    EnumIter,
129
    Display,
130
    PartialEq,
131
    Eq,
132
    Hash,
133
    Clone,
134
    Copy,
135
    Serialize,
136
    Deserialize,
137
    JsonSchema,
138
)]
139
pub enum SolverFamily {
140
    SAT,
141
    Minion,
142
}
143

            
144
/// The type for user-defined callbacks for use with [Solver].
145
///
146
/// Note that this enforces thread safety
147
pub type SolverCallback = Box<dyn Fn(HashMap<Name, Literal>) -> bool + Send>;
148
pub type SolverMutCallback =
149
    Box<dyn Fn(HashMap<Name, Literal>, Box<dyn ModelModifier>) -> bool + Send>;
150

            
151
/// A common interface for calling underlying solver APIs inside a [`Solver`].
152
///
153
/// Implementations of this trait aren't directly callable and should be used through [`Solver`] .
154
///
155
/// The below documentation lists the formal requirements that all implementations of
156
/// [`SolverAdaptor`] should follow - **see the top level module documentation and [`Solver`] for
157
/// usage details.**
158
///
159
/// # Encapsulation
160
///  
161
///  The [`SolverAdaptor`] trait **must** only be implemented inside a submodule of this one,
162
///  and **should** only be called through [`Solver`].
163
///
164
/// The `private::Sealed` trait and `private::Internal` type enforce these requirements by only
165
/// allowing trait implementations and calling of methods of SolverAdaptor to occur inside this
166
/// module.
167
///
168
/// # Thread Safety
169
///
170
/// Multiple instances of [`Solver`] can be run in parallel across multiple threads.
171
///
172
/// [`Solver`] provides no concurrency control or thread-safety; therefore, adaptors **must**
173
/// ensure that multiple instances of themselves can be ran in parallel. This applies to all
174
/// stages of solving including having two active `solve()` calls happening at a time, loading
175
/// a model while another is mid-solve, loading two models at once, etc.
176
///
177
/// A [SolverAdaptor] **may** use whatever threading or process model it likes underneath the hood,
178
/// as long as it obeys the above.
179
///
180
/// Method calls **should** block instead of erroring where possible.
181
///
182
/// Underlying solvers that only have one instance per process (such as Minion) **should** block
183
/// (eg. using a [`Mutex<()>`](`std::sync::Mutex`)) to run calls to
184
/// [`Solver<A,ModelLoaded>::solve()`] and [`Solver<A,ModelLoaded>::solve_mut()`] sequentially.
185
pub trait SolverAdaptor: private::Sealed + Any {
186
    /// Runs the solver on the given model.
187
    ///
188
    /// Implementations of this function **must** call the user provided callback whenever a solution
189
    /// is found. If the user callback returns `true`, search should continue, if the user callback
190
    /// returns `false`, search should terminate.
191
    ///
192
    /// # Returns
193
    ///
194
    /// If the solver terminates without crashing a [SolveSuccess] struct **must** returned. The
195
    /// value of [SearchStatus] can be used to denote whether the underlying solver completed its
196
    /// search or not. The latter case covers most non-crashing "failure" cases including user
197
    /// termination, timeouts, etc.
198
    ///
199
    /// To help populate [SearchStatus], it may be helpful to implement counters that track if the
200
    /// user callback has been called yet, and its return value. This information makes it is
201
    /// possible to distinguish between the most common search statuses:
202
    /// [SearchComplete::HasSolutions], [SearchComplete::NoSolutions], and
203
    /// [SearchIncomplete::UserTerminated].
204
    fn solve(
205
        &mut self,
206
        callback: SolverCallback,
207
        _: private::Internal,
208
    ) -> Result<SolveSuccess, SolverError>;
209

            
210
    /// Runs the solver on the given model, allowing modification of the model through a
211
    /// [`ModelModifier`].
212
    ///
213
    /// Implementations of this function **must** return [`OpNotSupported`](`ModificationFailure::OpNotSupported`)
214
    /// if modifying the model mid-search is not supported.
215
    ///
216
    /// Otherwise, this should work in the same way as [`solve`](SolverAdaptor::solve).
217
    fn solve_mut(
218
        &mut self,
219
        callback: SolverMutCallback,
220
        _: private::Internal,
221
    ) -> Result<SolveSuccess, SolverError>;
222
    fn load_model(&mut self, model: Model, _: private::Internal) -> Result<(), SolverError>;
223
316
    fn init_solver(&mut self, _: private::Internal) {}
224

            
225
    /// Get the solver family that this solver adaptor belongs to
226
    fn get_family(&self) -> SolverFamily;
227

            
228
    /// Gets the name of the solver adaptor for pretty printing.
229
    fn get_name(&self) -> Option<String> {
230
        None
231
    }
232

            
233
    /// Adds the solver adaptor name and family (if they exist) to the given stats object.
234
316
    fn add_adaptor_info_to_stats(&self, stats: SolverStats) -> SolverStats {
235
316
        SolverStats {
236
316
            solver_adaptor: self.get_name(),
237
316
            solver_family: Some(self.get_family()),
238
316
            ..stats
239
316
        }
240
316
    }
241
}
242

            
243
/// An abstract representation of a constraints solver.
244
///
245
/// [Solver] provides a common interface for interacting with a constraint solver. It also
246
/// abstracts over solver-specific datatypes, handling the translation to/from [conjure_core::ast]
247
/// types for a model and its solutions.
248
///
249
/// Details of how a model is solved is specified by the [SolverAdaptor]. This includes: the
250
/// underlying solver used, the translation of the model to a solver compatible form, how solutions
251
/// are translated back to [conjure_core::ast] types, and how incremental solving is implemented.
252
/// As such, there may be multiple [SolverAdaptor] implementations for a single underlying solver:
253
/// e.g. one adaptor may give solutions in a representation close to the solvers, while another may
254
/// attempt to rewrite it back into Essence.
255
///
256
#[derive(Clone)]
257
pub struct Solver<A: SolverAdaptor, State: SolverState = Init> {
258
    state: State,
259
    adaptor: A,
260
    context: Option<Arc<RwLock<Context<'static>>>>,
261
}
262

            
263
impl<Adaptor: SolverAdaptor> Solver<Adaptor> {
264
316
    pub fn new(solver_adaptor: Adaptor) -> Solver<Adaptor> {
265
316
        let mut solver = Solver {
266
316
            state: Init,
267
316
            adaptor: solver_adaptor,
268
316
            context: None,
269
316
        };
270
316

            
271
316
        solver.adaptor.init_solver(private::Internal);
272
316
        solver
273
316
    }
274

            
275
    pub fn get_family(&self) -> SolverFamily {
276
        self.adaptor.get_family()
277
    }
278
}
279

            
280
impl<A: SolverAdaptor> Solver<A, Init> {
281
316
    pub fn load_model(mut self, model: Model) -> Result<Solver<A, ModelLoaded>, SolverError> {
282
316
        let solver_model = &mut self.adaptor.load_model(model.clone(), private::Internal)?;
283
316
        Ok(Solver {
284
316
            state: ModelLoaded,
285
316
            adaptor: self.adaptor,
286
316
            context: Some(model.context.clone()),
287
316
        })
288
316
    }
289
}
290

            
291
impl<A: SolverAdaptor> Solver<A, ModelLoaded> {
292
316
    pub fn solve(
293
316
        mut self,
294
316
        callback: SolverCallback,
295
316
    ) -> Result<Solver<A, ExecutionSuccess>, SolverError> {
296
316
        #[allow(clippy::unwrap_used)]
297
316
        let start_time = Instant::now();
298
316

            
299
316
        #[allow(clippy::unwrap_used)]
300
316
        let result = self.adaptor.solve(callback, private::Internal);
301
316

            
302
316
        let duration = start_time.elapsed();
303
316

            
304
316
        match result {
305
316
            Ok(x) => {
306
316
                let stats = self
307
316
                    .adaptor
308
316
                    .add_adaptor_info_to_stats(x.stats)
309
316
                    .with_timings(duration.as_secs_f64());
310
316

            
311
316
                Ok(Solver {
312
316
                    adaptor: self.adaptor,
313
316
                    state: ExecutionSuccess {
314
316
                        stats,
315
316
                        status: x.status,
316
316
                        _sealed: private::Internal,
317
316
                    },
318
316
                    context: self.context,
319
316
                })
320
            }
321
            Err(x) => Err(x),
322
        }
323
316
    }
324

            
325
    pub fn solve_mut(
326
        mut self,
327
        callback: SolverMutCallback,
328
    ) -> Result<Solver<A, ExecutionSuccess>, SolverError> {
329
        #[allow(clippy::unwrap_used)]
330
        let start_time = Instant::now();
331

            
332
        #[allow(clippy::unwrap_used)]
333
        let result = self.adaptor.solve_mut(callback, private::Internal);
334

            
335
        let duration = start_time.elapsed();
336

            
337
        match result {
338
            Ok(x) => {
339
                let stats = self
340
                    .adaptor
341
                    .add_adaptor_info_to_stats(x.stats)
342
                    .with_timings(duration.as_secs_f64());
343

            
344
                Ok(Solver {
345
                    adaptor: self.adaptor,
346
                    state: ExecutionSuccess {
347
                        stats,
348
                        status: x.status,
349
                        _sealed: private::Internal,
350
                    },
351
                    context: self.context,
352
                })
353
            }
354
            Err(x) => Err(x),
355
        }
356
    }
357
}
358

            
359
impl<A: SolverAdaptor> Solver<A, ExecutionSuccess> {
360
245
    pub fn stats(&self) -> SolverStats {
361
245
        self.state.stats.clone()
362
245
    }
363

            
364
    // Saves this solvers stats to the global context as a "solver run"
365
245
    pub fn save_stats_to_context(&self) {
366
245
        #[allow(clippy::unwrap_used)]
367
245
        #[allow(clippy::expect_used)]
368
245
        self.context
369
245
            .as_ref()
370
245
            .expect("")
371
245
            .write()
372
245
            .unwrap()
373
245
            .stats
374
245
            .add_solver_run(self.stats());
375
245
    }
376

            
377
    pub fn wall_time_s(&self) -> f64 {
378
        self.stats().conjure_solver_wall_time_s
379
    }
380
}
381

            
382
/// Errors returned by [Solver] on failure.
383
#[non_exhaustive]
384
#[derive(Debug, Error, Clone)]
385
pub enum SolverError {
386
    #[error("operation not implemented yet: {0}")]
387
    OpNotImplemented(String),
388

            
389
    #[error("operation not supported: {0}")]
390
    OpNotSupported(String),
391

            
392
    #[error("model feature not supported: {0}")]
393
    ModelFeatureNotSupported(String),
394

            
395
    #[error("model feature not implemented yet: {0}")]
396
    ModelFeatureNotImplemented(String),
397

            
398
    // use for semantics / type errors, use the above for syntax
399
    #[error("model invalid: {0}")]
400
    ModelInvalid(String),
401

            
402
    #[error("error during solver execution: not implemented: {0}")]
403
    RuntimeNotImplemented(String),
404

            
405
    #[error("error during solver execution: {0}")]
406
    Runtime(String),
407
}
408

            
409
/// Returned from [SolverAdaptor] when solving is successful.
410
pub struct SolveSuccess {
411
    stats: SolverStats,
412
    status: SearchStatus,
413
}
414

            
415
pub enum SearchStatus {
416
    /// The search was complete (i.e. the solver found all possible solutions)
417
    Complete(SearchComplete),
418
    /// The search was incomplete (i.e. it was terminated before all solutions were found)
419
    Incomplete(SearchIncomplete),
420
}
421

            
422
#[non_exhaustive]
423
pub enum SearchIncomplete {
424
    Timeout,
425
    UserTerminated,
426
    #[doc(hidden)]
427
    /// This variant should not be matched - it exists to simulate non-exhaustiveness of this enum.
428
    __NonExhaustive,
429
}
430

            
431
#[non_exhaustive]
432
pub enum SearchComplete {
433
    HasSolutions,
434
    NoSolutions,
435
    #[doc(hidden)]
436
    /// This variant should not be matched - it exists to simulate non-exhaustiveness of this enum.
437
    __NonExhaustive,
438
}