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

chrjabs / rustsat / 21951866298

12 Feb 2026 03:01PM UTC coverage: 61.8% (+1.0%) from 60.815%
21951866298

Pull #598

github

web-flow
Merge d9a91723d into 0a46d8409
Pull Request #598: Major parser rewrite

1504 of 1769 new or added lines in 20 files covered. (85.02%)

29 existing lines in 9 files now uncovered.

14130 of 22864 relevant lines covered (61.8%)

140895.71 hits per line

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

83.11
/src/types.rs
1
//! # Common Types for SAT Solving
2
//!
3
//! Common types used throughout the library to guarantee type safety.
4

5
use core::ffi::c_int;
6
use std::{
7
    fmt, io,
8
    ops::{self, Index, IndexMut},
9
    path::Path,
10
};
11

12
use thiserror::Error;
13

14
pub mod constraints;
15
pub use constraints::{Cl, Clause};
16
use winnow::Parser;
17

18
use crate::{
19
    instances::fio::{self, ParsingError, SolverOutput},
20
    utils,
21
};
22

23
/// The hash map to use throughout the library
24
#[cfg(feature = "fxhash")]
25
pub type RsHashMap<K, V> = rustc_hash::FxHashMap<K, V>;
26
/// The hash map to use throughout the library
27
#[cfg(not(feature = "fxhash"))]
28
pub type RsHashMap<K, V> = std::collections::HashMap<K, V>;
29

30
/// The hash set to use throughout the library
31
#[cfg(feature = "fxhash")]
32
pub type RsHashSet<V> = rustc_hash::FxHashSet<V>;
33
/// The hash set to use throughout the library
34
#[cfg(not(feature = "fxhash"))]
35
pub type RsHashSet<V> = std::collections::HashSet<V>;
36

37
/// The hasher to use throughout the library
38
#[cfg(feature = "fxhash")]
39
pub type RsHasher = rustc_hash::FxHasher;
40
/// The hasher to use throughout the library
41
#[cfg(not(feature = "fxhash"))]
42
pub type RsHasher = std::collections::hash_map::DefaultHasher;
43

44
/// [`u32`] with an upper bound, mainly used for safe `serde::Deserialize`
45
#[derive(Hash, Eq, PartialEq, PartialOrd, Clone, Copy, Ord)]
46
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47
#[cfg_attr(feature = "serde", serde(try_from = "u32"))]
48
#[repr(transparent)]
49
struct LimitedU32<const U: u32>(u32);
50

51
impl<const U: u32> TryFrom<u32> for LimitedU32<U> {
52
    type Error = LimitedU32Error<U>;
53

54
    fn try_from(value: u32) -> Result<Self, Self::Error> {
2✔
55
        if value > U {
2✔
56
            Err(LimitedU32Error(()))
1✔
57
        } else {
58
            Ok(Self(value))
1✔
59
        }
60
    }
2✔
61
}
62

63
/// Error when creating a limited range [`u32`] and the value exceeds the limit
64
#[derive(thiserror::Error, Debug)]
65
struct LimitedU32Error<const U: u32>(());
66

67
impl<const U: u32> std::fmt::Display for LimitedU32Error<U> {
68
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1✔
69
        write!(f, "value must at most be {U}")
1✔
70
    }
1✔
71
}
72

73
const MAX_VAR_IDX: u32 = (u32::MAX - 1) / 2;
74

75
/// Type representing boolean variables in a SAT problem. Variables indexing in
76
/// RustSAT starts from 0 and the maximum index is `(u32::MAX - 1) / 2`. This is
77
/// because literals are represented as a single `u32` as well. The memory
78
/// representation of variables is `u32`.
79
#[derive(Hash, Eq, PartialEq, PartialOrd, Clone, Copy, Ord)]
80
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
81
#[repr(transparent)]
82
// this is fine because we are going through `LimitedU32`, which is deserialized via `try_from`
83
#[allow(clippy::unsafe_derive_deserialize)]
84
pub struct Var {
85
    idx: LimitedU32<MAX_VAR_IDX>,
86
}
87

88
impl Var {
89
    /// The maximum index that can be represented.
90
    pub const MAX_IDX: u32 = MAX_VAR_IDX;
91

92
    /// Creates a new variables with a given index.
93
    /// Indices start from 0.
94
    ///
95
    /// # Panics
96
    ///
97
    /// If `idx > Var::MAX_IDX`.
98
    #[must_use]
99
    pub const fn new(idx: u32) -> Var {
24,939✔
100
        assert!(idx <= Var::MAX_IDX, "variable index too high");
24,939✔
101
        Var {
24,939✔
102
            idx: LimitedU32(idx),
24,939✔
103
        }
24,939✔
104
    }
24,939✔
105

106
    /// Creates a new variables with a given index.
107
    /// Indices start from 0.
108
    ///
109
    /// # Errors
110
    ///
111
    /// `TypeError::IdxTooHigh(idx, Var::MAX_IDX)` if `idx > Var::MAX_IDX`.
112
    pub fn new_with_error(idx: u32) -> Result<Var, TypeError> {
×
113
        Ok(Var {
114
            idx: idx
×
115
                .try_into()
×
116
                .map_err(|_| TypeError::IdxTooHigh(idx, Var::MAX_IDX))?,
×
117
        })
118
    }
×
119

120
    /// Creates a new variables with a given index.
121
    /// Indices start from 0.
122
    /// Does not perform any check on the index, therefore might produce an inconsistent variable.
123
    /// Only use this for performance reasons if you are sure that `idx <= Var::MAX_IDX`.
124
    ///
125
    /// # Safety
126
    ///
127
    /// `idx` must be guaranteed to be not higher than `Var::MAX_IDX`
128
    #[inline]
129
    #[must_use]
130
    pub const unsafe fn new_unchecked(idx: u32) -> Var {
47,233,337✔
131
        debug_assert!(idx <= Var::MAX_IDX);
47,233,337✔
132
        Var {
47,233,337✔
133
            idx: LimitedU32(idx),
47,233,337✔
134
        }
47,233,337✔
135
    }
47,233,337✔
136

137
    /// Creates a literal with a given negation from the variable
138
    ///
139
    /// # Examples
140
    ///
141
    /// ```
142
    /// # use rustsat::types::{Var,Lit};
1✔
143
    /// let var = Var::new(5);
144
    /// let lit = Lit::positive(5);
1✔
145
    /// assert_eq!(lit, var.lit(false));
1✔
146
    /// ```
1✔
147
    #[inline]
1✔
148
    #[must_use]
149
    pub const fn lit(self, negated: bool) -> Lit {
173✔
150
        unsafe { Lit::new_unchecked(self.idx.0, negated) }
173✔
151
    }
173✔
152

153
    /// Creates a literal that is not negated.
154
    ///
155
    /// # Examples
156
    ///
157
    /// ```
158
    /// # use rustsat::types::{Var,Lit};
1✔
159
    /// let var = Var::new(5);
160
    /// let lit = Lit::positive(5);
1✔
161
    /// assert_eq!(lit, var.pos_lit());
1✔
162
    /// ```
1✔
163
    #[inline]
1✔
164
    #[must_use]
165
    pub const fn pos_lit(self) -> Lit {
5,234✔
166
        unsafe { Lit::positive_unchecked(self.idx.0) }
5,234✔
167
    }
5,234✔
168

169
    /// Creates a negated literal.
170
    ///
171
    /// # Examples
172
    ///
173
    /// ```
174
    /// # use rustsat::types::{Var,Lit};
1✔
175
    /// let var = Var::new(5);
176
    /// let lit = Lit::negative(5);
1✔
177
    /// assert_eq!(lit, var.neg_lit());
1✔
178
    /// ```
1✔
179
    #[inline]
1✔
180
    #[must_use]
181
    pub const fn neg_lit(self) -> Lit {
6✔
182
        unsafe { Lit::negative_unchecked(self.idx.0) }
6✔
183
    }
6✔
184

185
    /// Returns the index of the variable. This is a `usize` to enable easier
186
    /// indexing of data structures like vectors, even though the internal
187
    /// representation of a variable is `u32`. For the 32 bit index use
188
    /// [`Var::idx32`].
189
    ///
190
    /// # Examples
191
    ///
192
    /// ```
193
    /// # use rustsat::types::Var;
1✔
194
    /// let var = Var::new(5);
195
    /// assert_eq!(5, var.idx());
1✔
196
    /// ```
1✔
197
    #[inline]
1✔
198
    #[must_use]
199
    pub fn idx(self) -> usize {
29,538,141✔
200
        self.idx.0 as usize
29,538,141✔
201
    }
29,538,141✔
202

203
    /// Returns the 32 bit index of the variable.
204
    ///
205
    /// # Examples
206
    ///
207
    /// ```
208
    /// # use rustsat::types::Var;
1✔
209
    /// let var = Var::new(5);
210
    /// assert_eq!(5, var.idx32());
1✔
211
    /// ```
1✔
212
    #[inline]
1✔
213
    #[must_use]
214
    pub fn idx32(self) -> u32 {
9,916,746✔
215
        self.idx.0
9,916,746✔
216
    }
9,916,746✔
217

218
    /// Converts the variable to an integer as accepted by
219
    /// [IPASIR](https://github.com/biotomas/ipasir) and the [DIMACS file
220
    /// format](http://www.satcompetition.org/2011/format-benchmarks2011.html). The IPASIR variable
221
    /// will have `idx+1`.
222
    ///
223
    /// # Panics
224
    ///
225
    /// If the variable index does not fit in `c_int`. As [`c_int` will almost always be
226
    /// `i32`](https://doc.rust-lang.org/std/os/raw/type.c_int.html), this is only the case on very
227
    /// esoteric platforms.
228
    #[must_use]
229
    pub fn to_ipasir(self) -> c_int {
6✔
230
        (self.idx32() + 1)
6✔
231
            .try_into()
6✔
232
            .expect("variable index too high to fit in c_int")
6✔
233
    }
6✔
234

235
    /// Converts the variable to an integer as accepted by
236
    /// [IPASIR](https://github.com/biotomas/ipasir) and the [DIMACS file
237
    /// format](http://www.satcompetition.org/2011/format-benchmarks2011.html). The IPASIR literal
238
    /// will have `idx+1` and be negative if the literal is negated.
239
    ///
240
    /// # Errors
241
    ///
242
    /// `TypeError::IdxTooHigh(_, _)` if the literal does not fit into a `c_int`. As [`c_int` will
243
    /// almost always be `i32`](https://doc.rust-lang.org/std/os/raw/type.c_int.html), it is mostly
244
    /// safe to simply use [`Self::to_ipasir`] instead.
245
    pub fn to_ipasir_with_error(self) -> Result<c_int, TypeError> {
×
246
        (self.idx32() + 1)
×
247
            .try_into()
×
248
            .map_err(|_| TypeError::IdxTooHigh(self.idx32() + 1, c_int::MAX as u32))
×
249
    }
×
250
}
251

252
/// Incrementing variables
253
impl ops::Add<u32> for Var {
254
    type Output = Var;
255

256
    fn add(self, rhs: u32) -> Self::Output {
18,821,119✔
257
        let idx = self.idx.0 + rhs;
18,821,119✔
258
        debug_assert!(idx <= Var::MAX_IDX, "variable index overflow");
18,821,119✔
259
        Var {
18,821,117✔
260
            idx: LimitedU32(idx),
18,821,117✔
261
        }
18,821,117✔
262
    }
18,821,117✔
263
}
264

265
impl ops::AddAssign<u32> for Var {
266
    fn add_assign(&mut self, rhs: u32) {
5,244✔
267
        debug_assert!(self.idx.0 + rhs <= Var::MAX_IDX, "variable index overflow");
5,244✔
268
        self.idx.0 += rhs;
5,242✔
269
    }
5,242✔
270
}
271

272
/// Decrementing variables
273
impl ops::Sub<u32> for Var {
274
    type Output = Var;
275

276
    fn sub(self, rhs: u32) -> Self::Output {
48✔
277
        Var {
48✔
278
            idx: LimitedU32(self.idx.0 - rhs),
48✔
279
        }
48✔
280
    }
48✔
281
}
282

283
impl ops::SubAssign<u32> for Var {
284
    fn sub_assign(&mut self, rhs: u32) {
×
285
        self.idx.0 -= rhs;
×
286
    }
×
287
}
288

289
/// Bitwise operations: these should only be used for bit tricks where the underlying bit
290
/// representation does not matter
291
impl ops::BitOr<Var> for Var {
292
    type Output = Var;
293

294
    fn bitor(self, rhs: Var) -> Self::Output {
×
295
        // SAFETY: since the highest bit is never set, it will never be set in the result
296
        Var {
×
297
            idx: LimitedU32(self.idx.0 | rhs.idx.0),
×
298
        }
×
299
    }
×
300
}
301

302
impl ops::BitAnd<Var> for Var {
303
    type Output = Var;
304

305
    fn bitand(self, rhs: Var) -> Self::Output {
×
306
        // SAFETY: since the highest bit is never set, it will never be set in the result
307
        Var {
×
308
            idx: LimitedU32(self.idx.0 & rhs.idx.0),
×
309
        }
×
310
    }
×
311
}
312

313
impl ops::BitXor<Var> for Var {
314
    type Output = Var;
315

316
    fn bitxor(self, rhs: Var) -> Self::Output {
2✔
317
        // SAFETY: since the highest bit is never set, it will never be set in the result
318
        Var {
2✔
319
            idx: LimitedU32(self.idx.0 ^ rhs.idx.0),
2✔
320
        }
2✔
321
    }
2✔
322
}
323

324
/// Variables can be printed with the [`Display`](std::fmt::Display) trait
325
impl fmt::Display for Var {
326
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2,107✔
327
        if cfg!(feature = "ipasir-display") {
2,107✔
328
            write!(f, "x{}", self.to_ipasir())
×
329
        } else {
330
            write!(f, "x{}", self.idx())
2,107✔
331
        }
332
    }
2,107✔
333
}
334

335
/// Variables can be printed with the [`Debug`](std::fmt::Debug) trait
336
impl fmt::Debug for Var {
337
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
×
338
        write!(f, "x{}", self.idx.0)
×
339
    }
×
340
}
341

342
#[cfg(kani)]
343
impl kani::Arbitrary for Var {
344
    fn any() -> Self {
345
        let idx = u32::any();
346
        kani::assume(idx <= Var::MAX_IDX);
347
        Var::new(idx)
348
    }
349
}
350

351
/// More easily creates variables. Mainly used in tests.
352
///
353
/// # Examples
354
///
355
/// ```
356
/// # use rustsat::{var, types::Var};
1✔
357
/// assert_eq!(var![42], Var::new(42));
358
/// ```
1✔
359
#[macro_export]
1✔
360
macro_rules! var {
361
    ($v:expr) => {
362
        $crate::types::Var::new($v)
363
    };
364
}
365

366
/// Type representing literals, possibly negated boolean variables.
367
///
368
/// # Representation in Memory
369
///
370
/// Literal representation is `idx << 1` with the last bit representing
371
/// whether the literal is negated or not. This way the literal can directly
372
/// be used to index data structures with the two literals of a variable
373
/// being close together.
374
#[derive(Hash, Eq, PartialEq, PartialOrd, Ord, Clone, Copy)]
375
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
376
#[repr(transparent)]
377
// this is fine because there aren't any invalid `Lit` representations, only invalid `Var` ones
378
#[allow(clippy::unsafe_derive_deserialize)]
379
pub struct Lit {
380
    lidx: u32,
381
}
382

383
impl Lit {
384
    /// Represents a literal in memory
385
    #[inline]
386
    const fn represent(idx: u32, negated: bool) -> u32 {
20,481,535✔
387
        (idx << 1) + if negated { 1 } else { 0 }
20,481,535✔
388
    }
20,481,535✔
389

390
    /// Creates a new (negated or not) literal with a given index.
391
    ///
392
    /// # Panics
393
    ///
394
    /// If `idx > Var::MAX_IDX`.
395
    #[must_use]
396
    pub const fn new(idx: u32, negated: bool) -> Lit {
651,217✔
397
        assert!(idx < Var::MAX_IDX, "variable index too high");
651,217✔
398
        Lit {
651,217✔
399
            lidx: Lit::represent(idx, negated),
651,217✔
400
        }
651,217✔
401
    }
651,217✔
402

403
    /// Creates a new (negated or not) literal with a given index.
404
    ///
405
    /// # Errors
406
    ///
407
    /// `TypeError::IdxTooHigh(idx, Var::MAX_IDX)` if `idx > Var::MAX_IDX`.
408
    pub fn new_with_error(idx: u32, negated: bool) -> Result<Lit, TypeError> {
19,824,904✔
409
        if idx > Var::MAX_IDX {
19,824,904✔
410
            return Err(TypeError::IdxTooHigh(idx, Var::MAX_IDX));
×
411
        }
19,824,904✔
412
        Ok(Lit {
19,824,904✔
413
            lidx: Lit::represent(idx, negated),
19,824,904✔
414
        })
19,824,904✔
415
    }
19,824,904✔
416

417
    /// Creates a new (negated or not) literal with a given index.
418
    /// Does not perform any check on the index, therefore might produce an inconsistent variable.
419
    /// Only use this for performance reasons if you are sure that `idx <= Var::MAX_IDX`.
420
    ///
421
    /// # Safety
422
    ///
423
    /// `idx` must be guaranteed to be not higher than `Var::MAX_IDX`
424
    #[must_use]
425
    pub const unsafe fn new_unchecked(idx: u32, negated: bool) -> Lit {
5,413✔
426
        debug_assert!(idx <= Var::MAX_IDX);
5,413✔
427
        Lit {
5,413✔
428
            lidx: Lit::represent(idx, negated),
5,413✔
429
        }
5,413✔
430
    }
5,413✔
431

432
    /// Creates a new positive literal with a given index.
433
    /// Panics if `idx > Var::MAX_IDX`.
434
    #[inline]
435
    #[must_use]
436
    pub const fn positive(idx: u32) -> Lit {
651,000✔
437
        Lit::new(idx, false)
651,000✔
438
    }
651,000✔
439

440
    /// Creates a new negated literal with a given index.
441
    /// Panics if `idx > Var::MAX_IDX`.
442
    #[inline]
443
    #[must_use]
444
    pub const fn negative(idx: u32) -> Lit {
8✔
445
        Lit::new(idx, true)
8✔
446
    }
8✔
447

448
    /// Creates a new positive literal with a given index.
449
    ///
450
    /// # Errors
451
    ///
452
    /// If `idx > Var::MAX_IDX`.
453
    #[inline]
454
    pub fn positive_with_error(idx: u32) -> Result<Lit, TypeError> {
×
455
        Lit::new_with_error(idx, false)
×
456
    }
×
457

458
    /// Creates a new negated literal with a given index.
459
    ///
460
    /// # Errors
461
    ///
462
    /// If `idx > Var::MAX_IDX`.
463
    #[inline]
464
    pub fn negative_with_error(idx: u32) -> Result<Lit, TypeError> {
×
465
        Lit::new_with_error(idx, true)
×
466
    }
×
467

468
    /// Creates a new positive literal with a given index.
469
    /// Does not perform any check on the index, therefore might produce an inconsistent variable.
470
    /// Only use this for performance reasons if you are sure that `idx <= Var::MAX_IDX`.
471
    ///
472
    /// # Safety
473
    ///
474
    /// `idx` must be guaranteed to be not higher than `Var::MAX_IDX`
475
    #[inline]
476
    #[must_use]
477
    pub const unsafe fn positive_unchecked(idx: u32) -> Lit {
5,234✔
478
        Lit::new_unchecked(idx, false)
5,234✔
479
    }
5,234✔
480

481
    /// Creates a new negated literal with a given index.
482
    /// Does not perform any check on the index, therefore might produce an inconsistent variable.
483
    /// Only use this for performance reasons if you are sure that `idx <= Var::MAX_IDX`.
484
    ///
485
    /// # Safety
486
    ///
487
    /// `idx` must be guaranteed to be not higher than `Var::MAX_IDX`
488
    #[inline]
489
    #[must_use]
490
    pub const unsafe fn negative_unchecked(idx: u32) -> Lit {
6✔
491
        Lit::new_unchecked(idx, true)
6✔
492
    }
6✔
493

494
    /// Create a literal from an
495
    /// [IPASIR](https://github.com/biotomas/ipasir)/[DIMACS](http://www.satcompetition.org/2011/format-benchmarks2011.html)
496
    /// integer value.
497
    ///
498
    /// # Errors
499
    ///
500
    /// If the value is zero or the index too high.
501
    pub fn from_ipasir(val: c_int) -> Result<Lit, TypeError> {
27,235,578✔
502
        if val == 0 {
27,235,578✔
503
            return Err(TypeError::IpasirZero);
7,410,674✔
504
        }
19,824,904✔
505
        let negated = val < 0;
19,824,904✔
506
        let idx = val.unsigned_abs();
19,824,904✔
507
        Lit::new_with_error(idx - 1, negated)
19,824,904✔
508
    }
27,235,578✔
509

510
    /// Gets the variable index of the literal
511
    #[inline]
512
    #[must_use]
513
    pub fn vidx(self) -> usize {
47,788✔
514
        (self.lidx >> 1) as usize
47,788✔
515
    }
47,788✔
516

517
    /// Gets the 32-bit variable index of the literal
518
    #[inline]
519
    #[must_use]
520
    pub fn vidx32(self) -> u32 {
61,654,602✔
521
        self.lidx >> 1
61,654,602✔
522
    }
61,654,602✔
523

524
    /// Gets a literal representation for indexing data structures
525
    #[inline]
526
    #[must_use]
527
    pub fn lidx(self) -> usize {
×
528
        self.lidx as usize
×
529
    }
×
530

531
    /// Gets the variables that the literal corresponds to.
532
    ///
533
    /// # Examples
534
    ///
535
    /// ```
536
    /// # use rustsat::types::{Var,Lit};
1✔
537
    /// let var = Var::new(5);
538
    /// let lit = Lit::negative(5);
1✔
539
    /// assert_eq!(var, lit.var());
1✔
540
    /// ```
1✔
541
    #[inline]
1✔
542
    #[must_use]
543
    pub fn var(self) -> Var {
47,233,337✔
544
        unsafe { Var::new_unchecked(self.vidx32()) }
47,233,337✔
545
    }
47,233,337✔
546

547
    /// True if the literal is positive.
548
    #[inline]
549
    #[must_use]
550
    pub fn is_pos(self) -> bool {
1,383,470✔
551
        (self.lidx & 1u32) == 0
1,383,470✔
552
    }
1,383,470✔
553

554
    /// True if the literal is negated.
555
    #[inline]
556
    #[must_use]
557
    pub fn is_neg(self) -> bool {
27,823,490✔
558
        (self.lidx & 1u32) == 1
27,823,490✔
559
    }
27,823,490✔
560

561
    /// Converts the literal to an integer as accepted by
562
    /// [IPASIR](https://github.com/biotomas/ipasir) and the
563
    /// [DIMACS file format](http://www.satcompetition.org/2011/format-benchmarks2011.html). The
564
    /// IPASIR literal will have `idx+1` and be negative if the literal is negated. Panics if the
565
    /// literal does not fit into a `c_int`.
566
    ///
567
    /// # Panics
568
    ///
569
    /// If the variable index does not fit in `c_int`. As [`c_int` will almost always be
570
    /// `i32`](https://doc.rust-lang.org/std/os/raw/type.c_int.html), this is only the case on very
571
    /// esoteric platforms.
572
    #[must_use]
573
    pub fn to_ipasir(self) -> c_int {
14,031,608✔
574
        let negated = self.is_neg();
14,031,608✔
575
        let idx: c_int = (self.vidx32() + 1)
14,031,608✔
576
            .try_into()
14,031,608✔
577
            .expect("variable index too high to fit in c_int");
14,031,608✔
578
        if negated {
14,031,608✔
579
            -idx
7,299,803✔
580
        } else {
581
            idx
6,731,805✔
582
        }
583
    }
14,031,608✔
584

585
    /// Converts the literal to an integer as accepted by
586
    /// [IPASIR](https://github.com/biotomas/ipasir) and the [DIMACS file
587
    /// format](http://www.satcompetition.org/2011/format-benchmarks2011.html). The IPASIR literal
588
    /// will have `idx+1` and be negative if the literal is negated.
589
    ///
590
    /// # Errors
591
    ///
592
    /// `TypeError::IdxTooHigh(_, _)` if the literal does not fit into a `c_int`. As [`c_int`
593
    /// will almost always be `i32`](https://doc.rust-lang.org/std/os/raw/type.c_int.html), it is
594
    /// mostly safe to simply use [`Self::to_ipasir`] instead.
595
    pub fn to_ipasir_with_error(self) -> Result<c_int, TypeError> {
×
596
        let negated = self.is_neg();
×
597
        let idx: c_int = match (self.vidx() + 1).try_into() {
×
598
            Ok(idx) => idx,
×
599
            Err(_) => return Err(TypeError::IdxTooHigh(self.vidx32() + 1, c_int::MAX as u32)),
×
600
        };
601
        Ok(if negated { -idx } else { idx })
×
602
    }
×
603
}
604

605
/// Trait implementation allowing for negating literals with the `!` operator.
606
impl ops::Not for Lit {
607
    type Output = Lit;
608

609
    #[inline]
610
    fn not(self) -> Lit {
86,631✔
611
        Lit {
86,631✔
612
            lidx: self.lidx ^ 1u32,
86,631✔
613
        }
86,631✔
614
    }
86,631✔
615
}
616

617
/// Trait implementation allowing for negating literals with the unary `-` operator.
618
impl ops::Neg for Lit {
619
    type Output = Lit;
620

621
    #[inline]
622
    fn neg(self) -> Lit {
3✔
623
        Lit {
3✔
624
            lidx: self.lidx ^ 1u32,
3✔
625
        }
3✔
626
    }
3✔
627
}
628

629
/// Incrementing literals. This preserves the sign of the literal.
630
impl ops::Add<u32> for Lit {
631
    type Output = Lit;
632

633
    fn add(self, rhs: u32) -> Self::Output {
×
634
        Lit {
×
635
            lidx: self.lidx + 2 * rhs,
×
636
        }
×
637
    }
×
638
}
639

640
impl ops::AddAssign<u32> for Lit {
641
    fn add_assign(&mut self, rhs: u32) {
×
642
        self.lidx += 2 * rhs;
×
643
    }
×
644
}
645

646
/// Decrementing literals. This preserves the sign of the literal.
647
impl ops::Sub<u32> for Lit {
648
    type Output = Lit;
649

650
    fn sub(self, rhs: u32) -> Self::Output {
×
651
        Lit {
×
652
            lidx: self.lidx - 2 * rhs,
×
653
        }
×
654
    }
×
655
}
656

657
impl ops::SubAssign<u32> for Lit {
658
    fn sub_assign(&mut self, rhs: u32) {
×
659
        self.lidx -= 2 * rhs;
×
660
    }
×
661
}
662

663
/// Bitwise operations: these should only be used for bit tricks where the underlying bit
664
/// representation does not matter
665
impl ops::BitOr<Lit> for Lit {
666
    type Output = Lit;
667

668
    fn bitor(self, rhs: Lit) -> Self::Output {
×
669
        Lit {
×
670
            lidx: self.lidx | rhs.lidx,
×
671
        }
×
672
    }
×
673
}
674

675
impl ops::BitAnd<Lit> for Lit {
676
    type Output = Lit;
677

678
    fn bitand(self, rhs: Lit) -> Self::Output {
×
679
        Lit {
×
680
            lidx: self.lidx & rhs.lidx,
×
681
        }
×
682
    }
×
683
}
684

685
impl ops::BitXor<Lit> for Lit {
686
    type Output = Lit;
687

688
    fn bitxor(self, rhs: Lit) -> Self::Output {
2✔
689
        Lit {
2✔
690
            lidx: self.lidx ^ rhs.lidx,
2✔
691
        }
2✔
692
    }
2✔
693
}
694

695
/// Literals can be printed with the [`Display`](std::fmt::Display) trait
696
impl fmt::Display for Lit {
697
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2,093✔
698
        write!(f, "{}{}", if self.is_neg() { "~" } else { "" }, self.var())
2,093✔
699
    }
2,093✔
700
}
701

702
/// Literals can be printed with the [`Debug`](std::fmt::Debug) trait
703
impl fmt::Debug for Lit {
704
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2,980✔
705
        if self.is_neg() {
2,980✔
706
            write!(f, "~x{}", self.vidx())
2,664✔
707
        } else {
708
            write!(f, "x{}", self.vidx())
316✔
709
        }
710
    }
2,980✔
711
}
712

713
#[cfg(kani)]
714
impl kani::Arbitrary for Lit {
715
    fn any() -> Self {
716
        let var = Var::any();
717
        var.lit(bool::any())
718
    }
719
}
720

721
/// More easily creates literals. Mainly used in tests.
722
///
723
/// # Examples
724
///
725
/// ```
726
/// # use rustsat::{lit, types::Lit};
1✔
727
/// assert_eq!(lit![42], Lit::positive(42));
728
/// assert_eq!(!lit![42], Lit::negative(42));
1✔
729
/// ```
1✔
730
#[macro_export]
1✔
731
macro_rules! lit {
732
    ($l:expr) => {
733
        $crate::types::Lit::positive($l)
734
    };
735
}
736

737
/// More easily creates literals with
738
/// [IPASIR](https://github.com/biotomas/ipasir)/[DIMACS](http://www.satcompetition.org/2011/format-benchmarks2011.html)
739
/// indexing (starts from 1) and negation (negative value is negation). Mainly used in tests.
740
///
741
/// # Examples
742
///
743
/// ```
744
/// # use rustsat::{lit, ipasir_lit, types::Lit};
1✔
745
/// assert_eq!(ipasir_lit![42], lit![41]);
746
/// assert_eq!(ipasir_lit![-42], !lit![41]);
1✔
747
/// ```
1✔
748
#[macro_export]
1✔
749
macro_rules! ipasir_lit {
750
    ($l:expr) => {
751
        $crate::types::Lit::from_ipasir($l).unwrap()
752
    };
753
}
754

755
/// Ternary value assigned to a literal or variable, including possible "don't care"
756
#[derive(Clone, Copy, PartialEq, Eq, Default)]
757
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
758
#[repr(u8)]
759
pub enum TernaryVal {
760
    /// Positive assignment.
761
    True,
762
    /// Negative assignment.
763
    False,
764
    /// Formula is satisfied, no matter the assignment.
765
    #[default]
766
    DontCare,
767
}
768

769
impl TernaryVal {
770
    /// Converts a [`TernaryVal`] to a [`bool`] with a default value for "don't cares"
771
    #[must_use]
772
    pub fn to_bool_with_def(self, def: bool) -> bool {
6✔
773
        match self {
6✔
774
            TernaryVal::True => true,
2✔
775
            TernaryVal::False => false,
2✔
776
            TernaryVal::DontCare => def,
2✔
777
        }
778
    }
6✔
779
}
780

781
/// Ternary values can be printed with the [`Display`](std::fmt::Display) trait
782
impl fmt::Display for TernaryVal {
783
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
908✔
784
        match self {
908✔
785
            TernaryVal::True => write!(f, "1"),
275✔
786
            TernaryVal::False => write!(f, "0"),
633✔
787
            TernaryVal::DontCare => write!(f, "_"),
×
788
        }
789
    }
908✔
790
}
791

792
/// Ternary values can be printed with the [`Debug`](std::fmt::Debug) trait
793
impl fmt::Debug for TernaryVal {
794
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4,608✔
795
        match self {
4,608✔
796
            TernaryVal::True => write!(f, "1"),
2,304✔
797
            TernaryVal::False => write!(f, "0"),
2,304✔
798
            TernaryVal::DontCare => write!(f, "_"),
×
799
        }
800
    }
4,608✔
801
}
802

803
impl From<bool> for TernaryVal {
804
    fn from(value: bool) -> Self {
23,787✔
805
        if value {
23,787✔
806
            return TernaryVal::True;
11,796✔
807
        }
11,991✔
808
        TernaryVal::False
11,991✔
809
    }
23,787✔
810
}
811

812
impl ops::Not for TernaryVal {
813
    type Output = TernaryVal;
814

815
    fn not(self) -> Self::Output {
944✔
816
        match self {
944✔
817
            TernaryVal::True => TernaryVal::False,
472✔
818
            TernaryVal::False => TernaryVal::True,
472✔
819
            TernaryVal::DontCare => TernaryVal::DontCare,
×
820
        }
821
    }
944✔
822
}
823

824
impl ops::Neg for TernaryVal {
825
    type Output = TernaryVal;
826

827
    fn neg(self) -> Self::Output {
×
828
        match self {
×
829
            TernaryVal::True => TernaryVal::False,
×
830
            TernaryVal::False => TernaryVal::True,
×
831
            TernaryVal::DontCare => TernaryVal::DontCare,
×
832
        }
833
    }
×
834
}
835

836
#[cfg(kani)]
837
impl kani::Arbitrary for TernaryVal {
838
    fn any() -> Self {
839
        let val: u8 = kani::any();
840
        kani::assume(val < 3);
841
        match val {
842
            0 => TernaryVal::False,
843
            1 => TernaryVal::True,
844
            2 => TernaryVal::DontCare,
845
            _ => panic!(),
846
        }
847
    }
848
}
849

850
/// Possible errors in parsing a SAT solver value (`v`) line
851
#[derive(Error, Debug)]
852
pub enum InvalidVLine {
853
    /// The given `v` line starts with an invalid character
854
    #[error("The value line does not start with 'v ' but with {0}")]
855
    InvalidTag(char),
856
    /// The `v` line contains a conflicting assignment on the given variable
857
    #[error("The output of the SAT solver assigned different values to variable {0}")]
858
    ConflictingAssignment(Var),
859
    /// The given `v` line is empty
860
    #[error("Empty value line")]
861
    EmptyLine,
862
    /// Invalid character in MSE `v` line
863
    #[error("Invalid character in MSE v-line: {0}")]
864
    InvalidCharacter(char),
865
    /// Invalid integer string
866
    #[error("Invalid integer string: {0}")]
867
    InvalidInt(#[from] std::num::ParseIntError),
868
    /// Invalid IPASIR literal
869
    #[error("Invalid integer literal: {0}")]
870
    InvalidLit(#[from] TypeError),
871
    /// Error in parsing
872
    #[error("Parsing error: {0}")]
873
    Parsing(#[from] ParsingError),
874
}
875

876
/// V-line parsing or IO error
877
#[derive(Error, Debug)]
878
pub enum AssignmentFromSolverOutputError {
879
    /// Failed to parse solver output
880
    #[error("Invalid solver output: {0}")]
881
    InvalidOutput(#[from] fio::SatSolverOutputError),
882
    /// The solver did not return SAT
883
    #[error("the solver did not return SAT")]
884
    NotSat,
885
}
886

887
impl From<io::Error> for AssignmentFromSolverOutputError {
NEW
888
    fn from(value: io::Error) -> Self {
×
NEW
889
        Self::InvalidOutput(fio::SatSolverOutputError::from(value))
×
NEW
890
    }
×
891
}
892

893
/// Different possible `v`-line formats
894
enum VLineFormat {
895
    /// Assignment specified as a space-separated sequence of IPASIR literals. This is the format
896
    /// used in the SAT competition.
897
    SatComp,
898
    /// Assignment specified as a sequence of zeroes and ones. This is the format used in the
899
    /// MaxSAT Evaluation.
900
    MaxSatEval,
901
    /// Assignment specified as a sequence of space-separated OPB literals. This is the format used
902
    /// in the PB competition.
903
    PbComp,
904
}
905

906
/// Type representing an assignment of variables.
907
#[derive(Clone, PartialEq, Eq, Default)]
908
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
909
#[repr(transparent)]
910
pub struct Assignment {
911
    assignment: Vec<TernaryVal>,
912
}
913

914
impl Assignment {
915
    /// Gets the length of the assignment
916
    #[must_use]
917
    pub fn len(&self) -> usize {
1✔
918
        self.assignment.len()
1✔
919
    }
1✔
920

921
    /// Checks whether the assignment is empty
922
    #[must_use]
923
    pub fn is_empty(&self) -> bool {
×
924
        self.assignment.is_empty()
×
925
    }
×
926

927
    /// Get the value that the solution assigns to a variable.
928
    /// If the variable is not included in the solution, will return `TernaryVal::DontCare`.
929
    #[must_use]
930
    pub fn var_value(&self, var: Var) -> TernaryVal {
13,786,814✔
931
        if var.idx() >= self.assignment.len() {
13,786,814✔
932
            TernaryVal::DontCare
941,084✔
933
        } else {
934
            self.assignment[var.idx()]
12,845,730✔
935
        }
936
    }
13,786,814✔
937

938
    /// Same as [`Assignment::var_value`], but for literals.
939
    #[must_use]
940
    pub fn lit_value(&self, lit: Lit) -> TernaryVal {
13,786,802✔
941
        if lit.is_neg() {
13,786,802✔
942
            match self.var_value(lit.var()) {
7,353,450✔
943
                TernaryVal::DontCare => TernaryVal::DontCare,
778,897✔
944
                TernaryVal::True => TernaryVal::False,
930,777✔
945
                TernaryVal::False => TernaryVal::True,
5,643,776✔
946
            }
947
        } else {
948
            self.var_value(lit.var())
6,433,352✔
949
        }
950
    }
13,786,802✔
951

952
    /// Replaces unassigned variables in the assignment with a default value
953
    pub fn replace_dont_care(&mut self, def: bool) {
1✔
954
        self.assignment.iter_mut().for_each(|tv| {
3✔
955
            if tv == &TernaryVal::DontCare {
3✔
956
                if def {
1✔
957
                    *tv = TernaryVal::True;
1✔
958
                } else {
1✔
959
                    *tv = TernaryVal::False;
×
960
                }
×
961
            }
2✔
962
        });
3✔
963
    }
1✔
964

965
    /// Assigns a variable in the assignment
966
    pub fn assign_var(&mut self, variable: Var, value: TernaryVal) {
964,872✔
967
        if self.assignment.len() < variable.idx() + 1 {
964,872✔
968
            self.assignment
964,872✔
969
                .resize(variable.idx() + 1, TernaryVal::DontCare);
964,872✔
970
        }
964,872✔
971
        self.assignment[variable.idx()] = value;
964,872✔
972
    }
964,872✔
973

974
    /// Assigns a literal to true
975
    pub fn assign_lit(&mut self, lit: Lit) {
941,085✔
976
        let val = if lit.is_pos() {
941,085✔
977
            TernaryVal::True
162,188✔
978
        } else {
979
            TernaryVal::False
778,897✔
980
        };
981
        self.assign_var(lit.var(), val);
941,085✔
982
    }
941,085✔
983

984
    /// Truncates a solution to only include assignments up to a maximum variable
985
    #[must_use]
986
    pub fn truncate(mut self, max_var: Var) -> Self {
19✔
987
        self.assignment.truncate(max_var.idx() + 1);
19✔
988
        self
19✔
989
    }
19✔
990

991
    /// Get the maximum variable in the assignment
992
    ///
993
    /// # Panics
994
    ///
995
    /// If the assignment contains more then `u32::MAX` variables.
996
    #[must_use]
997
    pub fn max_var(&self) -> Option<Var> {
×
998
        if self.assignment.is_empty() {
×
999
            None
×
1000
        } else {
1001
            Some(var![
×
1002
                u32::try_from(self.assignment.len())
×
1003
                    .expect("assignment contains more than `u32::MAX` variables")
×
1004
                    - 1
×
1005
            ])
×
1006
        }
1007
    }
×
1008

1009
    /// Reads a solution from SAT solver output given the path
1010
    ///
1011
    /// If it is unclear whether the SAT solver indicated satisfiability, use [`fio::parse_sat_solver_output`] instead.
1012
    ///
1013
    /// # Errors
1014
    ///
1015
    /// - IO error: [`std::io::Error`]
1016
    /// - Invalid solver output: [`fio::SatSolverOutputError`]
1017
    /// - Invalid v line: [`InvalidVLine`]
NEW
1018
    pub fn from_solver_output_path<P: AsRef<Path>>(
×
NEW
1019
        path: P,
×
NEW
1020
    ) -> Result<Self, AssignmentFromSolverOutputError> {
×
NEW
1021
        let mut reader = std::io::BufReader::new(fio::open_compressed_uncompressed_read(path)?);
×
1022
        let output = fio::parse_sat_solver_output(&mut reader)?;
×
1023
        match output {
×
1024
            SolverOutput::Sat(solution) => Ok(solution),
×
NEW
1025
            _ => Err(AssignmentFromSolverOutputError::NotSat),
×
1026
        }
1027
    }
×
1028

1029
    /// Creates an assignment from a SAT solver value line
1030
    ///
1031
    /// # Errors
1032
    ///
1033
    /// [`InvalidVLine`] or if parsing fails.
1034
    pub fn from_vline(line: &str) -> Result<Self, InvalidVLine> {
72✔
1035
        let mut assignment = Assignment::default();
72✔
1036
        assignment.extend_from_vline(line)?;
72✔
1037
        Ok(assignment)
69✔
1038
    }
72✔
1039

1040
    /// Parses an assignment from a value line returned by a solver
1041
    ///
1042
    /// The following value line formats are supported:
1043
    /// - [SAT Competition](https://satcompetition.github.io/2024/output.html): `v 1 -2 -3 4 0`
1044
    /// - [MaxSAT Evaluation](https://maxsat-evaluations.github.io/2024/rules.html): `v 1001`
1045
    /// - [PB Competition](https://www.cril.univ-artois.fr/PB24/competitionRequirements.pdf): `v x1 -x2 -x3 x4`
1046
    ///
1047
    /// # Errors
1048
    ///
1049
    /// Can return various parsing errors
1050
    pub fn extend_from_vline(&mut self, lines: &str) -> Result<(), InvalidVLine> {
82,161✔
1051
        if lines.is_empty() {
82,161✔
1052
            return Err(InvalidVLine::EmptyLine);
1✔
1053
        }
82,160✔
1054
        // determine line format
1055
        let format = if lines.contains('x') {
82,160✔
1056
            VLineFormat::PbComp
2✔
1057
        } else if !lines[1..].trim().contains(' ')
82,158✔
1058
            && lines[1..].trim() != "0"
6✔
1059
            && lines[1..]
2✔
1060
                .trim()
2✔
1061
                .chars()
2✔
1062
                .try_for_each(|c| {
12✔
1063
                    if c == '1' || c == '0' {
12✔
1064
                        Ok(())
12✔
1065
                    } else {
1066
                        Err(())
×
1067
                    }
1068
                })
12✔
1069
                .is_ok()
2✔
1070
        {
1071
            VLineFormat::MaxSatEval
2✔
1072
        } else {
1073
            VLineFormat::SatComp
82,156✔
1074
        };
1075

1076
        for line in lines.lines() {
82,161✔
1077
            if let Some(tag) = line.chars().next() {
82,161✔
1078
                if tag != 'v' {
82,161✔
1079
                    return Err(InvalidVLine::InvalidTag(tag));
2✔
1080
                }
82,159✔
1081
            } else {
NEW
1082
                return Err(InvalidVLine::EmptyLine);
×
1083
            }
1084

1085
            match format {
82,159✔
1086
                VLineFormat::SatComp => self.extend_sat_comp_vline(line),
82,155✔
1087
                VLineFormat::MaxSatEval => self.extend_maxsat_eval_vline(line),
2✔
1088
                VLineFormat::PbComp => self.extend_pb_comp_vline(line),
2✔
1089
            }?;
2✔
1090
        }
1091
        Ok(())
82,156✔
1092
    }
82,161✔
1093

1094
    /// Parses a single SAT Competition v-line
1095
    fn extend_sat_comp_vline(&mut self, line: &str) -> Result<(), InvalidVLine> {
82,155✔
1096
        let line = &line[1..];
82,155✔
1097
        for number in line.split_whitespace() {
941,143✔
1098
            let number = number.parse::<i32>()?;
941,143✔
1099

1100
            // End of the value lines
1101
            if number == 0 {
941,141✔
1102
                continue;
68✔
1103
            }
941,073✔
1104

1105
            let literal = Lit::from_ipasir(number)?;
941,073✔
1106
            let val = self.lit_value(literal);
941,073✔
1107
            if val == TernaryVal::True && literal.is_neg()
941,073✔
1108
                || val == TernaryVal::False && literal.is_pos()
941,073✔
1109
            {
1110
                // Catch conflicting assignments
NEW
1111
                return Err(InvalidVLine::ConflictingAssignment(literal.var()));
×
1112
            }
941,073✔
1113
            self.assign_lit(literal);
941,073✔
1114
        }
1115
        Ok(())
82,153✔
1116
    }
82,155✔
1117

1118
    /// Parses a single MaxSAT Evaluation v-line
1119
    fn extend_maxsat_eval_vline(&mut self, line: &str) -> Result<(), InvalidVLine> {
2✔
1120
        let line = line[1..].trim();
2✔
1121
        self.assignment.reserve(line.len());
2✔
1122
        for char in line.chars() {
12✔
1123
            match char {
12✔
1124
                '0' => self.assignment.push(TernaryVal::False),
6✔
1125
                '1' => self.assignment.push(TernaryVal::True),
6✔
NEW
1126
                char => return Err(InvalidVLine::InvalidCharacter(char)),
×
1127
            }
1128
        }
1129
        Ok(())
2✔
1130
    }
2✔
1131

1132
    /// Parses a single PB Competition v-line
1133
    fn extend_pb_comp_vline(&mut self, line: &str) -> Result<(), InvalidVLine> {
2✔
1134
        let line = &line[1..];
2✔
1135
        for number in line.split_whitespace() {
10✔
1136
            let lit = fio::opb::literal(fio::opb::Options::default())
10✔
1137
                .parse(number)
10✔
1138
                .map_err(|e| {
10✔
NEW
1139
                    ParsingError::from_parse(
×
NEW
1140
                        &e,
×
NEW
1141
                        line,
×
NEW
1142
                        utils::substr_offset(line, number).unwrap(),
×
1143
                        1,
1144
                    )
NEW
1145
                })?;
×
1146
            let val = self.lit_value(lit);
10✔
1147
            if val == TernaryVal::True && lit.is_neg() || val == TernaryVal::False && lit.is_pos() {
10✔
1148
                // Catch conflicting assignments
NEW
1149
                return Err(InvalidVLine::ConflictingAssignment(lit.var()));
×
1150
            }
10✔
1151
            self.assign_lit(lit);
10✔
1152
        }
1153
        Ok(())
2✔
1154
    }
2✔
1155

1156
    /// Gets an iterator over literals assigned to true
1157
    #[expect(clippy::cast_possible_truncation)]
1158
    pub fn iter(&self) -> impl Iterator<Item = Lit> + '_ {
×
1159
        self.assignment
×
1160
            .iter()
×
1161
            .enumerate()
×
1162
            .filter_map(|(idx, tv)| match tv {
×
1163
                TernaryVal::True => Some(Lit::new(idx as u32, false)),
×
1164
                TernaryVal::False => Some(Lit::new(idx as u32, true)),
×
1165
                TernaryVal::DontCare => None,
×
1166
            })
×
1167
    }
×
1168
}
1169

1170
#[cfg(kani)]
1171
impl Assignment {
1172
    /// Generates a random assignment with the given number of variables
1173
    pub fn arbitrary(n_vars: u32) -> Self {
1174
        Self::from_iter((0..n_vars).map(|_| <bool as kani::Arbitrary>::any()))
1175
    }
1176
}
1177

1178
impl fmt::Debug for Assignment {
1179
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148✔
1180
        self.assignment.iter().try_for_each(|tv| write!(f, "{tv}"))
908✔
1181
    }
148✔
1182
}
1183

1184
impl fmt::Display for Assignment {
1185
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
×
1186
        self.assignment.iter().try_for_each(|tv| write!(f, "{tv}"))
×
1187
    }
×
1188
}
1189

1190
/// Turns the assignment into an iterator over all true literals
1191
impl IntoIterator for Assignment {
1192
    type Item = Lit;
1193

1194
    type IntoIter = std::iter::FilterMap<
1195
        std::iter::Enumerate<std::vec::IntoIter<TernaryVal>>,
1196
        fn((usize, TernaryVal)) -> Option<Lit>,
1197
    >;
1198

1199
    fn into_iter(self) -> Self::IntoIter {
1✔
1200
        self.assignment
1✔
1201
            .into_iter()
1✔
1202
            .enumerate()
1✔
1203
            .filter_map(|(idx, tv)| match tv {
6✔
1204
                TernaryVal::True => Some(Var::new(idx.try_into().unwrap()).pos_lit()),
3✔
1205
                TernaryVal::False => Some(Var::new(idx.try_into().unwrap()).neg_lit()),
2✔
1206
                TernaryVal::DontCare => None,
1✔
1207
            })
6✔
1208
    }
1✔
1209
}
1210

1211
/// Turns the assignment reference into an iterator over all true literals
1212
impl<'a> IntoIterator for &'a Assignment {
1213
    type Item = Lit;
1214

1215
    type IntoIter = std::iter::FilterMap<
1216
        std::iter::Enumerate<std::slice::Iter<'a, TernaryVal>>,
1217
        fn((usize, &TernaryVal)) -> Option<Lit>,
1218
    >;
1219

1220
    fn into_iter(self) -> Self::IntoIter {
1✔
1221
        self.assignment
1✔
1222
            .iter()
1✔
1223
            .enumerate()
1✔
1224
            .filter_map(|(idx, tv)| match tv {
6✔
1225
                TernaryVal::True => Some(Var::new(idx.try_into().unwrap()).pos_lit()),
3✔
1226
                TernaryVal::False => Some(Var::new(idx.try_into().unwrap()).neg_lit()),
2✔
1227
                TernaryVal::DontCare => None,
1✔
1228
            })
6✔
1229
    }
1✔
1230
}
1231

1232
impl FromIterator<Lit> for Assignment {
1233
    fn from_iter<T: IntoIterator<Item = Lit>>(iter: T) -> Self {
1✔
1234
        let mut assignment = Assignment::default();
1✔
1235
        iter.into_iter().for_each(|l| assignment.assign_lit(l));
2✔
1236
        assignment
1✔
1237
    }
1✔
1238
}
1239

1240
impl FromIterator<TernaryVal> for Assignment {
1241
    fn from_iter<T: IntoIterator<Item = TernaryVal>>(iter: T) -> Self {
×
1242
        Self::from(iter.into_iter().collect::<Vec<_>>())
×
1243
    }
×
1244
}
1245

1246
impl FromIterator<bool> for Assignment {
1247
    fn from_iter<T: IntoIterator<Item = bool>>(iter: T) -> Self {
×
1248
        iter.into_iter().map(TernaryVal::from).collect()
×
1249
    }
×
1250
}
1251

1252
impl From<Vec<TernaryVal>> for Assignment {
1253
    fn from(assignment: Vec<TernaryVal>) -> Self {
51✔
1254
        Self { assignment }
51✔
1255
    }
51✔
1256
}
1257

1258
impl Index<Var> for Assignment {
1259
    type Output = TernaryVal;
1260

1261
    fn index(&self, index: Var) -> &Self::Output {
2✔
1262
        &self.assignment[index.idx()]
2✔
1263
    }
2✔
1264
}
1265

1266
impl IndexMut<Var> for Assignment {
1267
    fn index_mut(&mut self, index: Var) -> &mut Self::Output {
×
1268
        &mut self.assignment[index.idx()]
×
1269
    }
×
1270
}
1271

1272
/// Errors related to types
1273
#[derive(Error, Debug)]
1274
pub enum TypeError {
1275
    /// The requested index is too high.
1276
    /// Contains the requested and the maximum index.
1277
    #[error("index {0} is too high (maximum {1})")]
1278
    IdxTooHigh(u32, u32),
1279
    /// IPASIR/DIMACS index is zero
1280
    #[error("zero is an invalid IPASIR/DIMACS literal")]
1281
    IpasirZero,
1282
}
1283

1284
/// An iterator over literals
1285
pub trait LitIter: IntoIterator<Item = Lit> {}
1286
impl<I: IntoIterator<Item = Lit>> LitIter for I {}
1287
/// An iterator over clauses
1288
pub trait ClsIter: IntoIterator<Item = Clause> {}
1289
impl<I: IntoIterator<Item = Clause>> ClsIter for I {}
1290

1291
/// An iterator over weighted literals
1292
pub trait WLitIter: IntoIterator<Item = (Lit, usize)> {}
1293
impl<I: IntoIterator<Item = (Lit, usize)>> WLitIter for I {}
1294
/// An iterator over weighted clauses
1295
pub trait WClsIter: IntoIterator<Item = (Clause, usize)> {}
1296
impl<I: IntoIterator<Item = (Clause, usize)>> WClsIter for I {}
1297
/// An iterator over integer-weighted literals
1298
pub trait IWLitIter: IntoIterator<Item = (Lit, isize)> {}
1299
impl<I: IntoIterator<Item = (Lit, isize)>> IWLitIter for I {}
1300

1301
#[cfg(feature = "proof-logging")]
1302
mod pigeons {
1303
    use std::fmt;
1304

1305
    /// A formatter for [`super::Var`] for use with the [`pigeons`] library to ensure same
1306
    /// variable formatting as in VeriPB
1307
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1308
    #[repr(transparent)]
1309
    pub struct PigeonVarFormatter(super::Var);
1310

1311
    impl From<super::Var> for PigeonVarFormatter {
1312
        fn from(value: super::Var) -> Self {
8,840✔
1313
            PigeonVarFormatter(value)
8,840✔
1314
        }
8,840✔
1315
    }
1316

1317
    impl fmt::Display for PigeonVarFormatter {
1318
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8,840✔
1319
            write!(f, "x{}", self.0.idx() + 1)
8,840✔
1320
        }
8,840✔
1321
    }
1322

1323
    impl pigeons::VarLike for super::Var {
1324
        type Formatter = PigeonVarFormatter;
1325
    }
1326

1327
    impl From<super::Lit> for pigeons::Axiom<super::Var> {
1328
        fn from(value: super::Lit) -> Self {
7,913✔
1329
            use pigeons::VarLike;
1330

1331
            if value.is_pos() {
7,913✔
1332
                value.var().pos_axiom()
3,937✔
1333
            } else {
1334
                value.var().neg_axiom()
3,976✔
1335
            }
1336
        }
7,913✔
1337
    }
1338
}
1339

1340
#[cfg(test)]
1341
mod tests {
1342
    use std::mem::size_of;
1343

1344
    use super::{Assignment, InvalidVLine, Lit, TernaryVal, Var};
1345

1346
    #[test]
1347
    fn var_index() {
1✔
1348
        let idx = 5;
1✔
1349
        let var = Var::new(idx);
1✔
1350
        assert_eq!(var.idx(), idx as usize);
1✔
1351
    }
1✔
1352

1353
    #[test]
1354
    fn var_index32() {
1✔
1355
        let idx = 5;
1✔
1356
        let var = Var::new(idx);
1✔
1357
        assert_eq!(var.idx32(), idx);
1✔
1358
    }
1✔
1359

1360
    #[test]
1361
    fn var_pos_lit() {
1✔
1362
        let idx = 5;
1✔
1363
        let var = Var::new(idx);
1✔
1364
        let lit = Lit::positive(idx);
1✔
1365
        assert_eq!(var.pos_lit(), lit);
1✔
1366
    }
1✔
1367

1368
    #[test]
1369
    fn var_neg_lit() {
1✔
1370
        let idx = 5;
1✔
1371
        let var = Var::new(idx);
1✔
1372
        let lit = Lit::negative(idx);
1✔
1373
        assert_eq!(var.neg_lit(), lit);
1✔
1374
    }
1✔
1375

1376
    #[test]
1377
    fn var_from_lit() {
1✔
1378
        let idx = 0;
1✔
1379
        let lit = Lit::positive(idx);
1✔
1380
        let var = Var::new(idx);
1✔
1381
        assert_eq!(lit.var(), var);
1✔
1382
    }
1✔
1383

1384
    #[cfg(feature = "proof-logging")]
1385
    #[test]
1386
    fn proof_log_var() {
1✔
1387
        use pigeons::VarLike;
1388
        let var = Var::new(3);
1✔
1389
        assert_eq!(&format!("{}", <Var as VarLike>::Formatter::from(var)), "x4");
1✔
1390
    }
1✔
1391

1392
    #[test]
1393
    #[should_panic(expected = "variable index overflow")]
1394
    fn var_add_1_overflow() {
1✔
1395
        let var = Var::new(Var::MAX_IDX);
1✔
1396
        let _ = var + 1;
1✔
1397
    }
1✔
1398

1399
    #[test]
1400
    #[should_panic(expected = "variable index overflow")]
1401
    fn var_add_42_overflow() {
1✔
1402
        let var = Var::new(Var::MAX_IDX - 41);
1✔
1403
        let _ = var + 42;
1✔
1404
    }
1✔
1405

1406
    #[test]
1407
    #[should_panic(expected = "variable index overflow")]
1408
    fn var_addassign_1_overflow() {
1✔
1409
        let mut var = Var::new(Var::MAX_IDX);
1✔
1410
        var += 1;
1✔
1411
    }
1✔
1412

1413
    #[test]
1414
    #[should_panic(expected = "variable index overflow")]
1415
    fn var_addassign_overflow() {
1✔
1416
        let mut var = Var::new(Var::MAX_IDX - 41);
1✔
1417
        var += 42;
1✔
1418
    }
1✔
1419

1420
    #[test]
1421
    fn lit_representation() {
1✔
1422
        let lidx = Lit::represent(5, true);
1✔
1423
        assert_eq!(lidx, 0b1011);
1✔
1424
    }
1✔
1425

1426
    #[test]
1427
    fn lit_is_pos() {
1✔
1428
        let lit = Lit::positive(0);
1✔
1429
        assert!(lit.is_pos());
1✔
1430
        assert!(!lit.is_neg());
1✔
1431
    }
1✔
1432

1433
    #[test]
1434
    fn lit_is_neg() {
1✔
1435
        let lit = Lit::negative(0);
1✔
1436
        assert!(!lit.is_pos());
1✔
1437
        assert!(lit.is_neg());
1✔
1438
    }
1✔
1439

1440
    #[test]
1441
    fn lit_negation() {
1✔
1442
        let lit1 = Lit::positive(0);
1✔
1443
        let lit2 = !lit1;
1✔
1444
        assert!(!lit2.is_pos());
1✔
1445
        assert!(lit2.is_neg());
1✔
1446
        assert_eq!(lit1.var(), lit2.var());
1✔
1447
    }
1✔
1448

1449
    #[test]
1450
    fn ipasir_lit_not_zero() {
1✔
1451
        let lit = Lit::positive(0);
1✔
1452
        assert_ne!(lit.to_ipasir(), 0);
1✔
1453
        let lit = !lit;
1✔
1454
        assert_ne!(lit.to_ipasir(), 0);
1✔
1455
    }
1✔
1456

1457
    #[test]
1458
    fn ipasir_lit_idx_plus_one() {
1✔
1459
        let idx = 5;
1✔
1460
        let lit = Lit::positive(idx);
1✔
1461
        assert_eq!(lit.to_ipasir(), i32::try_from(idx).unwrap() + 1);
1✔
1462
        let lit = !lit;
1✔
1463
        assert_eq!(lit.to_ipasir(), -(i32::try_from(idx).unwrap() + 1));
1✔
1464
    }
1✔
1465

1466
    #[test]
1467
    fn ternary_var_true() {
1✔
1468
        let tv = TernaryVal::True;
1✔
1469
        assert!(tv.to_bool_with_def(true));
1✔
1470
        assert!(tv.to_bool_with_def(false));
1✔
1471
    }
1✔
1472

1473
    #[test]
1474
    fn ternary_var_false() {
1✔
1475
        let tv = TernaryVal::False;
1✔
1476
        assert!(!tv.to_bool_with_def(true));
1✔
1477
        assert!(!tv.to_bool_with_def(false));
1✔
1478
    }
1✔
1479

1480
    #[test]
1481
    fn ternary_var_dnc() {
1✔
1482
        let tv = TernaryVal::DontCare;
1✔
1483
        assert!(tv.to_bool_with_def(true));
1✔
1484
        assert!(!tv.to_bool_with_def(false));
1✔
1485
    }
1✔
1486

1487
    #[test]
1488
    fn sol_var_val() {
1✔
1489
        let sol = Assignment::from(vec![
1✔
1490
            TernaryVal::True,
1✔
1491
            TernaryVal::False,
1✔
1492
            TernaryVal::DontCare,
1✔
1493
        ]);
1494
        let val = sol.var_value(Var::new(0));
1✔
1495
        assert_eq!(val, TernaryVal::True);
1✔
1496
        let val = sol.var_value(Var::new(1));
1✔
1497
        assert_eq!(val, TernaryVal::False);
1✔
1498
        let val = sol.var_value(Var::new(2));
1✔
1499
        assert_eq!(val, TernaryVal::DontCare);
1✔
1500
    }
1✔
1501

1502
    #[test]
1503
    fn sol_lit_val() {
1✔
1504
        let sol = Assignment::from(vec![
1✔
1505
            TernaryVal::True,
1✔
1506
            TernaryVal::False,
1✔
1507
            TernaryVal::DontCare,
1✔
1508
        ]);
1509
        let val = sol.lit_value(Lit::negative(0));
1✔
1510
        assert_eq!(val, TernaryVal::False);
1✔
1511
        let val = sol.lit_value(Lit::positive(0));
1✔
1512
        assert_eq!(val, TernaryVal::True);
1✔
1513
        let val = sol.lit_value(Lit::negative(1));
1✔
1514
        assert_eq!(val, TernaryVal::True);
1✔
1515
        let val = sol.lit_value(Lit::positive(1));
1✔
1516
        assert_eq!(val, TernaryVal::False);
1✔
1517
        let val = sol.lit_value(Lit::negative(2));
1✔
1518
        assert_eq!(val, TernaryVal::DontCare);
1✔
1519
        let val = sol.lit_value(Lit::positive(2));
1✔
1520
        assert_eq!(val, TernaryVal::DontCare);
1✔
1521
    }
1✔
1522

1523
    #[test]
1524
    fn sol_repl_dont_care() {
1✔
1525
        let mut sol = Assignment::from(vec![
1✔
1526
            TernaryVal::True,
1✔
1527
            TernaryVal::False,
1✔
1528
            TernaryVal::DontCare,
1✔
1529
        ]);
1530
        sol.replace_dont_care(true);
1✔
1531
        let val = sol.var_value(Var::new(0));
1✔
1532
        assert_eq!(val, TernaryVal::True);
1✔
1533
        let val = sol.var_value(Var::new(1));
1✔
1534
        assert_eq!(val, TernaryVal::False);
1✔
1535
        let val = sol.var_value(Var::new(2));
1✔
1536
        assert_eq!(val, TernaryVal::True);
1✔
1537
    }
1✔
1538

1539
    #[test]
1540
    fn sol_from_lits() {
1✔
1541
        let true_sol = Assignment::from(vec![
1✔
1542
            TernaryVal::True,
1✔
1543
            TernaryVal::DontCare,
1✔
1544
            TernaryVal::False,
1✔
1545
        ]);
1546
        let sol = Assignment::from_iter(vec![lit![0], !lit![2]]);
1✔
1547
        assert_eq!(true_sol, sol);
1✔
1548
    }
1✔
1549

1550
    #[test]
1551
    fn var_mem_size() {
1✔
1552
        assert_eq!(size_of::<Var>(), size_of::<u32>());
1✔
1553
    }
1✔
1554

1555
    #[test]
1556
    fn lit_mem_size() {
1✔
1557
        assert_eq!(size_of::<Lit>(), size_of::<u32>());
1✔
1558
    }
1✔
1559

1560
    #[test]
1561
    fn ternary_val_size() {
1✔
1562
        assert_eq!(size_of::<TernaryVal>(), 1);
1✔
1563
    }
1✔
1564

1565
    #[test]
1566
    fn parse_sat_comp_vline() {
1✔
1567
        let vline = "v 1 -2 4 -5 6 0";
1✔
1568
        let ground_truth = Assignment::from(vec![
1✔
1569
            TernaryVal::True,
1✔
1570
            TernaryVal::False,
1✔
1571
            TernaryVal::DontCare,
1✔
1572
            TernaryVal::True,
1✔
1573
            TernaryVal::False,
1✔
1574
            TernaryVal::True,
1✔
1575
        ]);
1576
        assert_eq!(Assignment::from_vline(vline).unwrap(), ground_truth);
1✔
1577
        assert_eq!(
1✔
1578
            {
1579
                let mut assign = Assignment::default();
1✔
1580
                assign.extend_from_vline(vline).unwrap();
1✔
1581
                assign
1✔
1582
            },
1583
            ground_truth
1584
        );
1585
    }
1✔
1586

1587
    #[test]
1588
    fn parse_maxsat_eval_vline() {
1✔
1589
        let vline = "v 100101";
1✔
1590
        let ground_truth = Assignment::from(vec![
1✔
1591
            TernaryVal::True,
1✔
1592
            TernaryVal::False,
1✔
1593
            TernaryVal::False,
1✔
1594
            TernaryVal::True,
1✔
1595
            TernaryVal::False,
1✔
1596
            TernaryVal::True,
1✔
1597
        ]);
1598
        assert_eq!(Assignment::from_vline(vline).unwrap(), ground_truth);
1✔
1599
        assert_eq!(
1✔
1600
            {
1601
                let mut assign = Assignment::default();
1✔
1602
                assign.extend_from_vline(vline).unwrap();
1✔
1603
                assign
1✔
1604
            },
1605
            ground_truth
1606
        );
1607
    }
1✔
1608

1609
    #[test]
1610
    fn parse_pb_comp_vline() {
1✔
1611
        let vline = "v x1 -x2 x4 -x5 x6";
1✔
1612
        let ground_truth = Assignment::from(vec![
1✔
1613
            TernaryVal::True,
1✔
1614
            TernaryVal::False,
1✔
1615
            TernaryVal::DontCare,
1✔
1616
            TernaryVal::True,
1✔
1617
            TernaryVal::False,
1✔
1618
            TernaryVal::True,
1✔
1619
        ]);
1620
        assert_eq!(Assignment::from_vline(vline).unwrap(), ground_truth);
1✔
1621
        assert_eq!(
1✔
1622
            {
1623
                let mut assign = Assignment::default();
1✔
1624
                assign.extend_from_vline(vline).unwrap();
1✔
1625
                assign
1✔
1626
            },
1627
            ground_truth
1628
        );
1629
    }
1✔
1630

1631
    #[test]
1632
    fn vline_invalid_lit_from() {
1✔
1633
        let vline = "v 1 -2 4 foo -5 bar 6 0";
1✔
1634
        let res = Assignment::from_vline(vline);
1✔
1635
        matches!(res.unwrap_err(), InvalidVLine::InvalidInt(_));
1✔
1636
    }
1✔
1637

1638
    #[test]
1639
    fn vline_invalid_lit_extend() {
1✔
1640
        let vline = "v 1 -2 4 foo -5 bar 6 0";
1✔
1641
        let mut assign = Assignment::default();
1✔
1642
        let res = assign.extend_from_vline(vline);
1✔
1643
        matches!(res.unwrap_err(), InvalidVLine::InvalidInt(_));
1✔
1644
    }
1✔
1645

1646
    #[test]
1647
    fn vline_invalid_tag_from() {
1✔
1648
        let vline = "b 1 -2 4 -5 6 0";
1✔
1649
        let res = Assignment::from_vline(vline);
1✔
1650
        assert!(matches!(res.unwrap_err(), InvalidVLine::InvalidTag('b')));
1✔
1651
    }
1✔
1652

1653
    #[test]
1654
    fn vline_invalid_tag_extend() {
1✔
1655
        let vline = "b 1 -2 4 -5 6 0";
1✔
1656
        let mut assign = Assignment::default();
1✔
1657
        let res = assign.extend_from_vline(vline);
1✔
1658
        assert!(matches!(res.unwrap_err(), InvalidVLine::InvalidTag('b')));
1✔
1659
    }
1✔
1660

1661
    #[test]
1662
    fn vline_invalid_empty() {
1✔
1663
        let vline = "";
1✔
1664
        let res = Assignment::from_vline(vline);
1✔
1665
        assert!(matches!(res.unwrap_err(), InvalidVLine::EmptyLine));
1✔
1666
    }
1✔
1667

1668
    #[test]
1669
    fn multi_vline() {
1✔
1670
        let vline = "v 1 2 3\nv 4 5 6 0";
1✔
1671
        let ground_truth = Assignment::from(vec![
1✔
1672
            TernaryVal::True,
1✔
1673
            TernaryVal::True,
1✔
1674
            TernaryVal::True,
1✔
1675
            TernaryVal::True,
1✔
1676
            TernaryVal::True,
1✔
1677
            TernaryVal::True,
1✔
1678
        ]);
1679
        let res = Assignment::from_vline(vline).unwrap();
1✔
1680
        assert_eq!(res, ground_truth);
1✔
1681
    }
1✔
1682

1683
    #[cfg(feature = "serde")]
1684
    #[test]
1685
    fn serde_var() {
1✔
1686
        let var = Var::new(5);
1✔
1687
        let json = serde_json::to_string(&var).unwrap();
1✔
1688
        assert_eq!(json, r#"{"idx":5}"#);
1✔
1689
        let roundtrip: Var = serde_json::from_str(&json).unwrap();
1✔
1690
        assert_eq!(roundtrip, var);
1✔
1691
    }
1✔
1692

1693
    #[cfg(feature = "serde")]
1694
    #[test]
1695
    fn serde_var_invalid() {
1✔
1696
        let failure: Result<Var, _> = serde_json::from_str("");
1✔
1697
        assert!(failure.is_err());
1✔
1698
        let failure: Result<Var, _> = serde_json::from_str(&format!("{{\"idx\":{}}}", u32::MAX));
1✔
1699
        assert!(failure.is_err());
1✔
1700
    }
1✔
1701

1702
    #[cfg(feature = "serde")]
1703
    #[test]
1704
    fn serde_lit() {
1✔
1705
        let lit = Lit::new(5, true);
1✔
1706
        let json = serde_json::to_string(&lit).unwrap();
1✔
1707
        assert_eq!(json, r#"{"lidx":11}"#);
1✔
1708
        let roundtrip: Lit = serde_json::from_str(&json).unwrap();
1✔
1709
        assert_eq!(roundtrip, lit);
1✔
1710
    }
1✔
1711

1712
    #[test]
1713
    fn assign_into_iter() {
1✔
1714
        let assign = Assignment::from(vec![
1✔
1715
            TernaryVal::True,
1✔
1716
            TernaryVal::False,
1✔
1717
            TernaryVal::True,
1✔
1718
            TernaryVal::DontCare,
1✔
1719
            TernaryVal::True,
1✔
1720
            TernaryVal::False,
1✔
1721
        ]);
1722
        let lits: Vec<_> = assign.into_iter().collect();
1✔
1723
        assert_eq!(lits, vec![lit![0], !lit![1], lit![2], lit![4], !lit![5]]);
1✔
1724
    }
1✔
1725

1726
    #[test]
1727
    fn assign_ref_into_iter() {
1✔
1728
        let assign = Assignment::from(vec![
1✔
1729
            TernaryVal::True,
1✔
1730
            TernaryVal::False,
1✔
1731
            TernaryVal::True,
1✔
1732
            TernaryVal::DontCare,
1✔
1733
            TernaryVal::True,
1✔
1734
            TernaryVal::False,
1✔
1735
        ]);
1736
        let lits: Vec<_> = (&assign).into_iter().collect();
1✔
1737
        assert_eq!(lits, vec![lit![0], !lit![1], lit![2], lit![4], !lit![5]]);
1✔
1738
        // ensure assign is still around, so we actually used a reference
1739
        assert_eq!(assign.len(), 6);
1✔
1740
    }
1✔
1741

1742
    #[test]
1743
    fn lit_branchless_swap() {
1✔
1744
        let a = Lit::positive(42);
1✔
1745
        let b = Lit::positive(58);
1✔
1746
        let should_be_b = a ^ b ^ a;
1✔
1747
        assert_eq!(b, should_be_b);
1✔
1748
    }
1✔
1749

1750
    #[test]
1751
    fn var_branchless_swap() {
1✔
1752
        let a = Var::new(42);
1✔
1753
        let b = Var::new(58);
1✔
1754
        let should_be_b = a ^ b ^ a;
1✔
1755
        assert_eq!(b, should_be_b);
1✔
1756
    }
1✔
1757
}
1758

1759
#[cfg(kani)]
1760
mod proofs {
1761
    #[kani::proof]
1762
    fn pos_lit() {
1763
        let var: super::Var = kani::any();
1764
        let lit = var.pos_lit();
1765
        assert_eq!(var, lit.var());
1766
    }
1767

1768
    #[kani::proof]
1769
    fn neg_lit() {
1770
        let var: super::Var = kani::any();
1771
        let lit = var.neg_lit();
1772
        assert_eq!(var, lit.var());
1773
    }
1774
}
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