• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

chrjabs / rustsat / 21958406699

12 Feb 2026 05:00PM UTC coverage: 61.964% (+1.1%) from 60.815%
21958406699

push

github

chrjabs
refactor: migrate `rustsat-tools` parsers to `winnow`

closes #564

241 of 301 new or added lines in 6 files covered. (80.07%)

1102 existing lines in 16 files now uncovered.

14178 of 22881 relevant lines covered (61.96%)

140791.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

45.08
/src/instances.rs
1
//! # Satisfiability and Optimization Instance Representations
2
//!
3
//! Types representing general satisfiability and optimization instances with
4
//! functionality to convert them to SAT or MaxSAT instances.
5

6
use std::{
7
    any::{Any, TypeId},
8
    hash::{Hash, Hasher},
9
    io,
10
};
11

12
use crate::{
13
    types::{Lit, RsHashMap, RsHasher, Var},
14
    var,
15
};
16

17
mod sat;
18
pub use sat::{Cnf, Instance as SatInstance};
19

20
#[cfg(feature = "optimization")]
21
mod opt;
22
#[cfg(feature = "optimization")]
23
pub use opt::{Instance as OptInstance, Objective};
24

25
#[cfg(feature = "multiopt")]
26
mod multiopt;
27
#[cfg(feature = "multiopt")]
28
pub use multiopt::MultiOptInstance;
29

30
pub mod fio;
31

32
/// Trait for variable managers keeping track of used variables
33
pub trait ManageVars {
34
    /// Uses up the next free variable
35
    fn new_var(&mut self) -> Var;
36
    /// Uses up the next free variable and returns its positive literal.
37
    fn new_lit(&mut self) -> Lit {
1,638✔
38
        self.new_var().pos_lit()
1,638✔
39
    }
1,638✔
40
    /// Gets the used variable with the highest index
41
    fn max_var(&self) -> Option<Var>;
42
    /// Increases the next free variable index if the provided variable has a
43
    /// higher index than the next variable in the manager.
44
    /// Returns true if the next free index has been increased and false otherwise.
45
    fn increase_next_free(&mut self, v: Var) -> bool;
46
    /// Marks variables up to the given one as used. Returns true if the next
47
    /// free index has been increased and false otherwise.
48
    fn mark_used(&mut self, v: Var) -> bool {
18,821,069✔
49
        self.increase_next_free(v + 1)
18,821,069✔
50
    }
18,821,069✔
51
    /// Combines two variable managers.
52
    /// In case an object is in both object maps, the one of `other` has precedence.
53
    fn combine(&mut self, other: Self)
54
    where
55
        Self: Sized;
56
    /// Gets the number of used variables. Typically this is just the index of
57
    /// the next free variable.
58
    fn n_used(&self) -> u32;
59
    /// Forget variables `>= min_var`
60
    fn forget_from(&mut self, min_var: Var);
61
}
62

63
/// Trait for variable managers re-indexing an existing instance
64
pub trait ReindexVars: ManageVars {
65
    /// Gets a remapped variable for an input variable or crates a new mapping
66
    fn reindex(&mut self, in_var: Var) -> Var;
67
    /// Gets a remapped literal for an input literal
68
    fn reindex_lit(&mut self, in_lit: Lit) -> Lit {
4✔
69
        let v = self.reindex(in_lit.var());
4✔
70
        if in_lit.is_pos() {
4✔
71
            v.pos_lit()
4✔
72
        } else {
UNCOV
73
            v.neg_lit()
×
74
        }
75
    }
4✔
76
    /// Reverses the re-indexing of a variable
77
    fn reverse(&self, out_var: Var) -> Option<Var>;
78
    /// Reverses the re-indexing of a literal
79
    fn reverse_lit(&self, out_lit: Lit) -> Option<Lit> {
×
80
        self.reverse(out_lit.var()).map(|v| {
×
81
            if out_lit.is_pos() {
×
UNCOV
82
                v.pos_lit()
×
83
            } else {
UNCOV
84
                v.neg_lit()
×
85
            }
86
        })
×
UNCOV
87
    }
×
88
}
89

90
/// Simple counting variable manager
91
#[derive(Debug, PartialEq, Eq, Clone)]
92
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
93
pub struct BasicVarManager {
94
    next_var: Var,
95
}
96

97
impl BasicVarManager {
98
    /// Creates a new variable manager from a next free variable
99
    #[must_use]
100
    pub fn from_next_free(next_var: Var) -> BasicVarManager {
64✔
101
        BasicVarManager { next_var }
64✔
102
    }
64✔
103
}
104

105
impl ManageVars for BasicVarManager {
106
    fn new_var(&mut self) -> Var {
5,242✔
107
        let v = self.next_var;
5,242✔
108
        self.next_var += 1;
5,242✔
109
        v
5,242✔
110
    }
5,242✔
111

112
    fn max_var(&self) -> Option<Var> {
48✔
113
        if self.next_var == var![0] {
48✔
UNCOV
114
            None
×
115
        } else {
116
            Some(self.next_var - 1)
48✔
117
        }
118
    }
48✔
119

120
    fn increase_next_free(&mut self, v: Var) -> bool {
18,821,376✔
121
        if v > self.next_var {
18,821,376✔
122
            self.next_var = v;
591✔
123
            return true;
591✔
124
        }
18,820,785✔
125
        false
18,820,785✔
126
    }
18,821,376✔
127

128
    fn combine(&mut self, other: Self) {
×
129
        if other.next_var > self.next_var {
×
130
            self.next_var = other.next_var;
×
131
        }
×
UNCOV
132
    }
×
133

134
    fn n_used(&self) -> u32 {
3,230✔
135
        self.next_var.idx32()
3,230✔
136
    }
3,230✔
137

138
    fn forget_from(&mut self, min_var: Var) {
×
139
        self.next_var = std::cmp::min(self.next_var, min_var);
×
UNCOV
140
    }
×
141
}
142

143
impl Default for BasicVarManager {
144
    fn default() -> Self {
377✔
145
        Self {
377✔
146
            next_var: Var::new(0),
377✔
147
        }
377✔
148
    }
377✔
149
}
150

151
/// Manager for re-indexing an existing instance
152
#[derive(PartialEq, Eq, Debug)]
153
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
154
pub struct ReindexingVarManager {
155
    next_var: Var,
156
    in_map: RsHashMap<Var, Var>,
157
    out_map: Vec<Var>,
158
}
159

160
impl ReindexingVarManager {
161
    /// Creates a new variable manager from a next free variable
162
    ///
163
    /// Will map all variables below `next_var` with the identity function
164
    #[must_use]
165
    pub fn from_next_free(next_var: Var) -> Self {
×
166
        Self {
×
167
            next_var,
×
168
            in_map: RsHashMap::default(),
×
169
            out_map: (0..next_var.idx32()).map(Var::new).collect(),
×
170
        }
×
UNCOV
171
    }
×
172
}
173

174
impl ReindexVars for ReindexingVarManager {
175
    fn reindex(&mut self, in_var: Var) -> Var {
8✔
176
        if let Some(v) = self.in_map.get(&in_var) {
8✔
177
            *v
4✔
178
        } else {
179
            let v = self.new_var();
4✔
180
            self.in_map.insert(in_var, v);
4✔
181
            self.out_map.push(in_var);
4✔
182
            v
4✔
183
        }
184
    }
8✔
185

186
    fn reverse(&self, out_var: Var) -> Option<Var> {
×
187
        if out_var.idx() >= self.out_map.len() {
×
188
            return None;
×
189
        }
×
190
        Some(self.out_map[out_var.idx()])
×
UNCOV
191
    }
×
192
}
193

194
impl Default for ReindexingVarManager {
195
    fn default() -> Self {
1✔
196
        Self {
1✔
197
            next_var: Var::new(0),
1✔
198
            in_map: RsHashMap::default(),
1✔
199
            out_map: Vec::default(),
1✔
200
        }
1✔
201
    }
1✔
202
}
203

204
impl ManageVars for ReindexingVarManager {
205
    fn new_var(&mut self) -> Var {
4✔
206
        let v = self.next_var;
4✔
207
        self.next_var = v + 1;
4✔
208
        v
4✔
209
    }
4✔
210

211
    fn max_var(&self) -> Option<Var> {
×
212
        if self.next_var == var![0] {
×
UNCOV
213
            None
×
214
        } else {
UNCOV
215
            Some(self.next_var - 1)
×
216
        }
UNCOV
217
    }
×
218

219
    fn increase_next_free(&mut self, v: Var) -> bool {
×
220
        if v > self.next_var {
×
221
            self.next_var = v;
×
222
            return true;
×
223
        }
×
224
        false
×
UNCOV
225
    }
×
226

227
    fn combine(&mut self, other: Self) {
×
228
        if other.next_var > self.next_var {
×
229
            self.next_var = other.next_var;
×
230
        }
×
231
        self.in_map.extend(other.in_map);
×
UNCOV
232
    }
×
233

234
    fn n_used(&self) -> u32 {
×
235
        self.next_var.idx32()
×
UNCOV
236
    }
×
237

238
    fn forget_from(&mut self, min_var: Var) {
×
239
        self.in_map.retain(|_, v| *v < min_var);
×
240
        self.out_map.truncate(min_var.idx() + 1);
×
241
        self.next_var = std::cmp::min(self.next_var, min_var);
×
UNCOV
242
    }
×
243
}
244

245
/// Manager keeping track of used variables and variables associated with objects
246
#[derive(PartialEq, Eq)]
247
#[expect(missing_debug_implementations)]
248
pub struct ObjectVarManager {
249
    next_var: Var,
250
    object_map: RsHashMap<Box<dyn VarKey>, Var>,
251
}
252

253
impl ObjectVarManager {
254
    /// Creates a new variable manager from a next free variable
255
    #[must_use]
256
    pub fn from_next_free(next_var: Var) -> Self {
×
257
        Self {
×
258
            next_var,
×
259
            object_map: RsHashMap::default(),
×
260
        }
×
UNCOV
261
    }
×
262

263
    /// Gets a variable associated with an object
264
    /// A new variable is used up if the object is seen for the first time
265
    pub fn object_var<T>(&mut self, obj: T) -> Var
3✔
266
    where
3✔
267
        T: Eq + Hash + 'static,
3✔
268
    {
269
        let key: Box<dyn VarKey> = Box::new(obj);
3✔
270
        if let Some(v) = self.object_map.get(&key) {
3✔
271
            *v
1✔
272
        } else {
273
            let v = self.new_var();
2✔
274
            self.object_map.insert(key, v);
2✔
275
            v
2✔
276
        }
277
    }
3✔
278
}
279

280
impl Default for ObjectVarManager {
281
    fn default() -> Self {
2✔
282
        Self {
2✔
283
            next_var: Var::new(0),
2✔
284
            object_map: RsHashMap::default(),
2✔
285
        }
2✔
286
    }
2✔
287
}
288

289
impl ManageVars for ObjectVarManager {
290
    fn new_var(&mut self) -> Var {
6✔
291
        let v = self.next_var;
6✔
292
        self.next_var = v + 1;
6✔
293
        v
6✔
294
    }
6✔
295

296
    fn max_var(&self) -> Option<Var> {
×
297
        if self.next_var == var![0] {
×
UNCOV
298
            None
×
299
        } else {
UNCOV
300
            Some(self.next_var - 1)
×
301
        }
UNCOV
302
    }
×
303

304
    fn increase_next_free(&mut self, v: Var) -> bool {
×
305
        if v > self.next_var {
×
306
            self.next_var = v;
×
307
            return true;
×
308
        }
×
309
        false
×
UNCOV
310
    }
×
311

312
    fn combine(&mut self, other: Self) {
×
313
        if other.next_var > self.next_var {
×
314
            self.next_var = other.next_var;
×
315
        }
×
316
        self.object_map.extend(other.object_map);
×
UNCOV
317
    }
×
318

319
    fn n_used(&self) -> u32 {
×
320
        self.next_var.idx32()
×
UNCOV
321
    }
×
322

323
    fn forget_from(&mut self, min_var: Var) {
×
324
        self.object_map.retain(|_, v| *v < min_var);
×
325
        self.next_var = std::cmp::min(self.next_var, min_var);
×
UNCOV
326
    }
×
327
}
328

329
#[cfg(feature = "rand")]
330
/// Manager for randomly re-indexing an instance
331
#[derive(PartialEq, Eq, Debug)]
332
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
333
pub struct RandReindVarManager {
334
    next_var: Var,
335
    in_map: Vec<Var>,
336
    out_map: Vec<Var>,
337
}
338

339
#[cfg(feature = "rand")]
340
impl RandReindVarManager {
341
    /// Creates a new variable manager from a next free variable
UNCOV
342
    pub fn init(n_vars: u32) -> Self {
×
343
        use rand::seq::SliceRandom;
344
        let mut in_map: Vec<Var> = (0..n_vars).map(Var::new).collect();
×
UNCOV
345
        let mut rng = rand::rng();
×
346
        // Build randomly shuffled input map
UNCOV
347
        in_map[..].shuffle(&mut rng);
×
348
        // Build reverse map
349
        let mut out_map = vec![Var::new(0); n_vars as usize];
×
350
        in_map.iter().enumerate().for_each(|(idx, v)| {
×
351
            out_map[v.idx()] = Var::new(crate::utils::unreachable_err!(u32::try_from(idx)));
×
352
        });
×
353
        Self {
×
354
            next_var: Var::new(n_vars),
×
355
            in_map,
×
356
            out_map,
×
357
        }
×
UNCOV
358
    }
×
359
}
360

361
#[cfg(feature = "rand")]
362
impl ReindexVars for RandReindVarManager {
363
    fn reindex(&mut self, in_var: Var) -> Var {
×
364
        match self.in_map.get(in_var.idx()) {
×
UNCOV
365
            Some(v) => *v,
×
366
            None => {
367
                // Don't reindex vars that are out of initialized range
UNCOV
368
                in_var
×
369
            }
370
        }
UNCOV
371
    }
×
372

373
    fn reverse(&self, out_var: Var) -> Option<Var> {
×
374
        match self.out_map.get(out_var.idx()) {
×
UNCOV
375
            Some(v) => Some(*v),
×
376
            None => {
377
                // Vars out of the initialized range are not reindexed
UNCOV
378
                Some(out_var)
×
379
            }
380
        }
UNCOV
381
    }
×
382
}
383

384
#[cfg(feature = "rand")]
385
impl ManageVars for RandReindVarManager {
386
    fn new_var(&mut self) -> Var {
×
387
        let v = self.next_var;
×
388
        self.next_var = v + 1;
×
389
        v
×
UNCOV
390
    }
×
391

392
    fn max_var(&self) -> Option<Var> {
×
393
        if self.next_var == var![0] {
×
UNCOV
394
            None
×
395
        } else {
UNCOV
396
            Some(self.next_var - 1)
×
397
        }
UNCOV
398
    }
×
399

400
    fn increase_next_free(&mut self, v: Var) -> bool {
×
401
        if v > self.next_var {
×
402
            self.next_var = v;
×
403
            return true;
×
404
        }
×
405
        false
×
UNCOV
406
    }
×
407

408
    fn combine(&mut self, other: Self) {
×
409
        if other.next_var > self.next_var {
×
410
            self.next_var = other.next_var;
×
411
        }
×
412
        self.in_map.extend(other.in_map);
×
UNCOV
413
    }
×
414

415
    fn n_used(&self) -> u32 {
×
416
        self.next_var.idx32()
×
UNCOV
417
    }
×
418

419
    fn forget_from(&mut self, min_var: Var) {
×
420
        self.next_var = std::cmp::min(self.next_var, min_var);
×
UNCOV
421
    }
×
422
}
423

424
/// Allows for a hash map with arbitrary key type:
425
/// <https://stackoverflow.com/a/64840069>
426
trait VarKey {
427
    fn eq(&self, other: &dyn VarKey) -> bool;
428
    fn hash(&self) -> u64;
429
    fn as_any(&self) -> &dyn Any;
430
}
431

432
impl<T: Eq + Hash + 'static> VarKey for T {
433
    fn eq(&self, other: &dyn VarKey) -> bool {
1✔
434
        if let Some(other) = other.as_any().downcast_ref::<T>() {
1✔
435
            return self == other;
1✔
436
        }
×
UNCOV
437
        false
×
438
    }
1✔
439

440
    fn hash(&self) -> u64 {
4✔
441
        let mut h = RsHasher::default();
4✔
442
        // mix the typeid of T into the hash to make distinct types
443
        // provide distinct hashes
444
        Hash::hash(&(TypeId::of::<T>(), self), &mut h);
4✔
445
        h.finish()
4✔
446
    }
4✔
447

448
    fn as_any(&self) -> &dyn Any {
1✔
449
        self
1✔
450
    }
1✔
451
}
452

453
impl PartialEq for Box<dyn VarKey> {
454
    fn eq(&self, other: &Self) -> bool {
1✔
455
        VarKey::eq(self.as_ref(), other.as_ref())
1✔
456
    }
1✔
457
}
458

459
impl Eq for Box<dyn VarKey> {}
460

461
impl Hash for Box<dyn VarKey> {
462
    fn hash<H: Hasher>(&self, state: &mut H) {
4✔
463
        let key_hash = VarKey::hash(self.as_ref());
4✔
464
        state.write_u64(key_hash);
4✔
465
    }
4✔
466
}
467

468
/// Errors when writing instances to DIMACS files
469
#[derive(Debug, thiserror::Error)]
470
pub enum WriteDimacsError {
471
    /// Input-output error
472
    #[error("IO error: {0}")]
473
    Io(#[from] io::Error),
474
    /// The instance is non-clausal
475
    #[error("writing to DIMACS files requires clausal constraints")]
476
    RequiresClausal,
477
}
478

479
/// Errors when writing instances to OPB files
480
#[cfg(feature = "optimization")]
481
#[derive(Debug, thiserror::Error)]
482
pub enum WriteOpbError {
483
    /// Input-output error
484
    #[error("IO error: {0}")]
485
    Io(#[from] io::Error),
486
    /// The instance is non-clausal
487
    #[error("writing to OPB files requires soft literal objectives")]
488
    RequiresSoftLits,
489
}
490

491
#[cfg(feature = "optimization")]
492
impl From<crate::RequiresSoftLits> for WriteOpbError {
UNCOV
493
    fn from(_: crate::RequiresSoftLits) -> Self {
×
UNCOV
494
        Self::RequiresSoftLits
×
UNCOV
495
    }
×
496
}
497

498
#[cfg(test)]
499
mod tests {
500
    use super::{ManageVars, ObjectVarManager};
501

502
    #[test]
503
    fn var_manager_sequence() {
1✔
504
        let mut man = ObjectVarManager::default();
1✔
505
        let v1 = man.new_var();
1✔
506
        let v2 = man.new_var();
1✔
507
        let v3 = man.new_var();
1✔
508
        let v4 = man.new_var();
1✔
509
        assert_eq!(v1.idx(), 0);
1✔
510
        assert_eq!(v2.idx(), 1);
1✔
511
        assert_eq!(v3.idx(), 2);
1✔
512
        assert_eq!(v4.idx(), 3);
1✔
513
    }
1✔
514

515
    #[test]
516
    fn var_manager_objects() {
1✔
517
        let mut man = ObjectVarManager::default();
1✔
518
        let obj1 = ("Test", 5);
1✔
519
        let obj2 = vec![3, 1, 6];
1✔
520
        let v1 = man.object_var(obj1);
1✔
521
        let v2 = man.object_var(obj2);
1✔
522
        let v3 = man.object_var(obj1);
1✔
523
        assert_ne!(v1, v2);
1✔
524
        assert_eq!(v1, v3);
1✔
525
    }
1✔
526
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc