Skip to main content

music21_rs/
sieve.rs

1//! Xenakis sieves, ported from music21's `sieve` module.
2//!
3//! A sieve is a logical expression over *residual classes*. `3@0` selects every
4//! integer congruent to 0 modulo 3; `|`, `&` and `^` combine classes as union,
5//! intersection and symmetric difference; `-` complements one; and `{}` or `()`
6//! group. Applied to semitones, the resulting integer set is a scale — the
7//! major scale is `(-3@2 & 4) | (-3@1 & 4@1) | (3@2 & 4@2) | (-3 & 4@3)`.
8//!
9//! What is here is the sieve itself — parsing an expression, testing
10//! membership, the segment formats and the interval widths of one period —
11//! and the number helpers music21 keeps beside it: primes by
12//! [`eratosthenes`] and [`rabin_miller`], and the unit-interval spacings
13//! [`unit_norm_range`], [`unit_norm_equal`] and [`unit_norm_step`]. Sieve
14//! compression and pitch-range realization stay music21's.
15
16use std::fmt;
17
18use crate::defaults::{FloatType, IntegerType, UnsignedIntegerType};
19use crate::error::{Error, Result};
20
21/// A parsed Xenakis sieve.
22///
23/// ```
24/// use music21_rs::Sieve;
25///
26/// // Every third semitone: a cycle of minor thirds.
27/// let sieve = Sieve::parse("3@0")?;
28/// assert_eq!(sieve.period(), 3);
29/// assert_eq!(sieve.interval_widths()?, [3]);
30///
31/// // The major scale, as Xenakis would write it.
32/// let major = Sieve::parse("(-3@2 & 4) | (-3@1 & 4@1) | (3@2 & 4@2) | (-3 & 4@3)")?;
33/// assert_eq!(major.interval_widths()?, [2, 2, 1, 2, 2, 2, 1]);
34/// # Ok::<(), music21_rs::Error>(())
35/// ```
36#[derive(Clone, Debug, Eq, PartialEq)]
37#[must_use]
38pub struct Sieve {
39    root: Node,
40}
41
42#[derive(Clone, Debug, Eq, PartialEq)]
43enum Node {
44    /// Integers congruent to `shift` modulo `modulus`.
45    Residual {
46        modulus: UnsignedIntegerType,
47        shift: UnsignedIntegerType,
48    },
49    Not(Box<Node>),
50    And(Box<Node>, Box<Node>),
51    Or(Box<Node>, Box<Node>),
52    Xor(Box<Node>, Box<Node>),
53    /// A group the expression was written with, `{}` or `()`.
54    ///
55    /// Kept because music21 writes a sieve back out as it was given, with
56    /// only the residuals normalized, so `(5|2)&4&8` reads back as
57    /// `{5@0|2@0}&4@0&8@0` and a combined sieve wraps each side in braces
58    /// whether or not the precedence needs them.
59    Group(Box<Node>),
60}
61
62impl Node {
63    fn contains(&self, z: IntegerType) -> bool {
64        match self {
65            Self::Residual { modulus, shift } => {
66                z.rem_euclid(*modulus as IntegerType) == *shift as IntegerType
67            }
68            Self::Group(inner) => inner.contains(z),
69            Self::Not(inner) => !inner.contains(z),
70            Self::And(left, right) => left.contains(z) && right.contains(z),
71            Self::Or(left, right) => left.contains(z) || right.contains(z),
72            Self::Xor(left, right) => left.contains(z) != right.contains(z),
73        }
74    }
75
76    /// The same tree with every residual's shift moved on by `n`.
77    ///
78    /// music21 passes an `n` down to each residual when it reads a segment,
79    /// which is the same thing: `3@2` read at `n = 10` selects the multiples
80    /// of three.
81    fn shifted(&self, n: IntegerType) -> Node {
82        match self {
83            Self::Residual { modulus, shift } => Self::Residual {
84                modulus: *modulus,
85                shift: (*shift as IntegerType + n).rem_euclid(*modulus as IntegerType)
86                    as UnsignedIntegerType,
87            },
88            Self::Group(inner) => Self::Group(Box::new(inner.shifted(n))),
89            Self::Not(inner) => Self::Not(Box::new(inner.shifted(n))),
90            Self::And(left, right) => {
91                Self::And(Box::new(left.shifted(n)), Box::new(right.shifted(n)))
92            }
93            Self::Or(left, right) => {
94                Self::Or(Box::new(left.shifted(n)), Box::new(right.shifted(n)))
95            }
96            Self::Xor(left, right) => {
97                Self::Xor(Box::new(left.shifted(n)), Box::new(right.shifted(n)))
98            }
99        }
100    }
101
102    fn collect_moduli(&self, out: &mut Vec<UnsignedIntegerType>) {
103        match self {
104            Self::Residual { modulus, .. } => out.push(*modulus),
105            Self::Group(inner) | Self::Not(inner) => inner.collect_moduli(out),
106            Self::And(left, right) | Self::Or(left, right) | Self::Xor(left, right) => {
107                left.collect_moduli(out);
108                right.collect_moduli(out);
109            }
110        }
111    }
112}
113
114impl Sieve {
115    /// Parses a sieve expression such as `"3@0|4@1"`.
116    ///
117    /// A bare modulus means a shift of zero, so `"5"` is `"5@0"`. Whitespace is
118    /// ignored, `{}` and `()` both group, and `&` binds tighter than `^`, which
119    /// binds tighter than `|` — matching music21, where `3@0|4@0&6@0` parses as
120    /// `3@0|{4@0&6@0}`.
121    pub fn parse(expression: &str) -> Result<Self> {
122        let tokens = tokenize(expression)?;
123        let mut parser = Parser {
124            tokens: &tokens,
125            position: 0,
126        };
127        let root = parser.parse_or()?;
128        if parser.position != tokens.len() {
129            return Err(Error::Sieve(format!(
130                "trailing input in sieve {expression:?} at token {}",
131                parser.position
132            )));
133        }
134        Ok(Self { root })
135    }
136
137    /// Returns whether an integer is in the sieve.
138    pub fn contains(&self, z: IntegerType) -> bool {
139        self.root.contains(z)
140    }
141
142    /// Returns the period: the least common multiple of every modulus.
143    ///
144    /// The sieve's membership pattern repeats with this length.
145    pub fn period(&self) -> UnsignedIntegerType {
146        let mut moduli = Vec::new();
147        self.root.collect_moduli(&mut moduli);
148        moduli.into_iter().fold(1, lcm)
149    }
150
151    /// Returns the members of the sieve in `low..=high`.
152    pub fn segment(&self, low: IntegerType, high: IntegerType) -> Vec<IntegerType> {
153        (low..=high).filter(|z| self.contains(*z)).collect()
154    }
155
156    /// Returns the widths between consecutive members of one period.
157    ///
158    /// This is music21's `PitchSieve.getIntervalSequence`, in semitones: the
159    /// sieve is evaluated over `0..=period` and the consecutive differences
160    /// taken. A sieve with fewer than two members in that window has no widths
161    /// and is an error, exactly as music21 raises for `3@1`.
162    pub fn interval_widths(&self) -> Result<Vec<IntegerType>> {
163        let period = self.period();
164        let members = self.segment(0, period as IntegerType);
165        if members.len() < 2 {
166            return Err(Error::Sieve(format!(
167                "sieve has {} member(s) in its period of {period}, so it defines no intervals",
168                members.len()
169            )));
170        }
171        Ok(members.windows(2).map(|pair| pair[1] - pair[0]).collect())
172    }
173
174    /// The same sieve with every residual's shift moved on by `n`.
175    ///
176    /// This is the `n` music21 takes beside a range when it reads a segment.
177    pub fn shifted(&self, n: IntegerType) -> Self {
178        Self {
179            root: self.root.shifted(n),
180        }
181    }
182
183    /// The sieve over `low..=high` as ones and noughts, one per integer in
184    /// the range rather than one per member.
185    ///
186    /// music21's `segmentFormat='binary'`.
187    pub fn segment_binary(&self, low: IntegerType, high: IntegerType) -> Vec<IntegerType> {
188        (low..=high)
189            .map(|z| IntegerType::from(self.contains(z)))
190            .collect()
191    }
192
193    /// The widths between consecutive members over `low..=high`, one shorter
194    /// than the segment itself.
195    ///
196    /// music21's `segmentFormat='width'`. Unlike [`Sieve::interval_widths`]
197    /// this reads whatever range it is given rather than one period, so it
198    /// says nothing about where the pattern repeats.
199    pub fn segment_widths(&self, low: IntegerType, high: IntegerType) -> Vec<IntegerType> {
200        let members = self.segment(low, high);
201        members.windows(2).map(|pair| pair[1] - pair[0]).collect()
202    }
203
204    /// Each member's place in `low..=high` as a fraction of the way across
205    /// it, so the range's own ends are nought and one.
206    ///
207    /// music21's `segmentFormat='unit'`. A range with no width answers nought
208    /// for every member, as music21 does rather than dividing by it.
209    pub fn segment_unit(&self, low: IntegerType, high: IntegerType) -> Vec<FloatType> {
210        let members = self.segment(low, high);
211        if members.len() < 2 {
212            return vec![0.0; members.len().min(1)];
213        }
214        let span = FloatType::from(high - low);
215        if span == 0.0 {
216            return vec![0.0; members.len()];
217        }
218        members
219            .into_iter()
220            .map(|member| FloatType::from(member - low) / span)
221            .collect()
222    }
223
224    /// The first `length` members at or above `z_minimum`, reading the sieve
225    /// shifted on by `n`.
226    ///
227    /// music21's `collect`, which walks upward a hundred integers at a time
228    /// until it has enough. The walk is bounded, so a sieve with too few
229    /// members to fill the length is an error rather than a loop that never
230    /// ends.
231    pub fn collect(
232        &self,
233        n: IntegerType,
234        z_minimum: IntegerType,
235        length: usize,
236    ) -> Result<Vec<IntegerType>> {
237        const STEP: IntegerType = 100;
238        const ROUNDS: usize = 10_000;
239
240        let shifted = self.shifted(n);
241        let mut found = Vec::with_capacity(length);
242        let mut low = z_minimum;
243        for _ in 0..ROUNDS {
244            found.extend(shifted.segment(low, low + STEP - 1));
245            if found.len() >= length {
246                found.truncate(length);
247                return Ok(found);
248            }
249            low += STEP;
250        }
251        Err(Error::Sieve(format!(
252            "desired length of {length} cannot be found in sieve {self}"
253        )))
254    }
255
256    /// The members of both sieves, written the way music21 writes a combined
257    /// sieve: each side in braces around the operator.
258    ///
259    /// Note the order. music21's `a & b` answers `{b}&{a}`, so the facade
260    /// calls this the other way round; the crate keeps the order a reader
261    /// would expect.
262    pub fn intersection(&self, other: &Self) -> Self {
263        self.combined(other, Node::And)
264    }
265
266    /// The members of either sieve. See [`Sieve::intersection`] for the
267    /// bracketing and the order.
268    pub fn union(&self, other: &Self) -> Self {
269        self.combined(other, Node::Or)
270    }
271
272    /// The members of one sieve or the other but not both. See
273    /// [`Sieve::intersection`] for the bracketing and the order.
274    pub fn symmetric_difference(&self, other: &Self) -> Self {
275        self.combined(other, Node::Xor)
276    }
277
278    fn combined(&self, other: &Self, join: fn(Box<Node>, Box<Node>) -> Node) -> Self {
279        Self {
280            root: join(
281                Box::new(Node::Group(Box::new(self.root.clone()))),
282                Box::new(Node::Group(Box::new(other.root.clone()))),
283            ),
284        }
285    }
286}
287
288impl fmt::Display for Node {
289    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290        match self {
291            Self::Residual { modulus, shift } => write!(f, "{modulus}@{shift}"),
292            Self::Group(inner) => write!(f, "{{{inner}}}"),
293            Self::Not(inner) => write!(f, "-{inner}"),
294            Self::And(left, right) => write!(f, "{left}&{right}"),
295            Self::Or(left, right) => write!(f, "{left}|{right}"),
296            Self::Xor(left, right) => write!(f, "{left}^{right}"),
297        }
298    }
299}
300
301/// Writes the sieve as music21 writes it: the expression it was given, with
302/// each residual normalized to `modulus@shift` and the groups it was written
303/// with kept.
304///
305/// ```
306/// use music21_rs::Sieve;
307///
308/// assert_eq!(Sieve::parse("3@11")?.to_string(), "3@2");
309/// assert_eq!(Sieve::parse("(5|2)&4&8")?.to_string(), "{5@0|2@0}&4@0&8@0");
310/// # Ok::<(), music21_rs::Error>(())
311/// ```
312impl fmt::Display for Sieve {
313    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314        write!(f, "{}", self.root)
315    }
316}
317
318/// The least common multiple, with nought where either side is nought.
319fn lcm(a: UnsignedIntegerType, b: UnsignedIntegerType) -> UnsignedIntegerType {
320    if a == 0 || b == 0 {
321        return 0;
322    }
323    num::integer::lcm(a, b)
324}
325
326#[derive(Clone, Copy, Debug, Eq, PartialEq)]
327enum Token {
328    Number(UnsignedIntegerType),
329    At,
330    Not,
331    And,
332    Or,
333    Xor,
334    Open,
335    Close,
336}
337
338fn tokenize(expression: &str) -> Result<Vec<Token>> {
339    let mut tokens = Vec::new();
340    let mut chars = expression.chars().peekable();
341
342    while let Some(&character) = chars.peek() {
343        match character {
344            ' ' | '\t' | '\n' | '\r' => {
345                chars.next();
346            }
347            '0'..='9' => {
348                let mut value: UnsignedIntegerType = 0;
349                while let Some(&digit) = chars.peek() {
350                    let Some(digit) = digit.to_digit(10) else {
351                        break;
352                    };
353                    value = value
354                        .checked_mul(10)
355                        .and_then(|value| value.checked_add(digit))
356                        .ok_or_else(|| {
357                            Error::Sieve(format!("number overflows in sieve {expression:?}"))
358                        })?;
359                    chars.next();
360                }
361                tokens.push(Token::Number(value));
362            }
363            '@' => {
364                chars.next();
365                tokens.push(Token::At);
366            }
367            '-' => {
368                chars.next();
369                tokens.push(Token::Not);
370            }
371            '&' => {
372                chars.next();
373                tokens.push(Token::And);
374            }
375            '|' => {
376                chars.next();
377                tokens.push(Token::Or);
378            }
379            '^' => {
380                chars.next();
381                tokens.push(Token::Xor);
382            }
383            '{' | '(' => {
384                chars.next();
385                tokens.push(Token::Open);
386            }
387            '}' | ')' => {
388                chars.next();
389                tokens.push(Token::Close);
390            }
391            other => {
392                return Err(Error::Sieve(format!(
393                    "unexpected character {other:?} in sieve {expression:?}"
394                )));
395            }
396        }
397    }
398
399    if tokens.is_empty() {
400        return Err(Error::Sieve("sieve expression is empty".to_string()));
401    }
402    Ok(tokens)
403}
404
405struct Parser<'a> {
406    tokens: &'a [Token],
407    position: usize,
408}
409
410impl Parser<'_> {
411    fn peek(&self) -> Option<Token> {
412        self.tokens.get(self.position).copied()
413    }
414
415    fn eat(&mut self, token: Token) -> bool {
416        if self.peek() == Some(token) {
417            self.position += 1;
418            return true;
419        }
420        false
421    }
422
423    fn parse_or(&mut self) -> Result<Node> {
424        let mut left = self.parse_xor()?;
425        while self.eat(Token::Or) {
426            let right = self.parse_xor()?;
427            left = Node::Or(Box::new(left), Box::new(right));
428        }
429        Ok(left)
430    }
431
432    fn parse_xor(&mut self) -> Result<Node> {
433        let mut left = self.parse_and()?;
434        while self.eat(Token::Xor) {
435            let right = self.parse_and()?;
436            left = Node::Xor(Box::new(left), Box::new(right));
437        }
438        Ok(left)
439    }
440
441    fn parse_and(&mut self) -> Result<Node> {
442        let mut left = self.parse_unary()?;
443        while self.eat(Token::And) {
444            let right = self.parse_unary()?;
445            left = Node::And(Box::new(left), Box::new(right));
446        }
447        Ok(left)
448    }
449
450    fn parse_unary(&mut self) -> Result<Node> {
451        if self.eat(Token::Not) {
452            return Ok(Node::Not(Box::new(self.parse_unary()?)));
453        }
454        self.parse_primary()
455    }
456
457    fn parse_primary(&mut self) -> Result<Node> {
458        if self.eat(Token::Open) {
459            let inner = self.parse_or()?;
460            if !self.eat(Token::Close) {
461                return Err(Error::Sieve("unclosed group in sieve".to_string()));
462            }
463            return Ok(Node::Group(Box::new(inner)));
464        }
465
466        let Some(Token::Number(modulus)) = self.peek() else {
467            return Err(Error::Sieve(format!(
468                "expected a modulus in sieve at token {}",
469                self.position
470            )));
471        };
472        self.position += 1;
473
474        if modulus == 0 {
475            return Err(Error::Sieve("sieve modulus must be non-zero".to_string()));
476        }
477
478        // A bare modulus means a shift of zero, as music21's `5` is `5@0`.
479        let shift = if self.eat(Token::At) {
480            let Some(Token::Number(shift)) = self.peek() else {
481                return Err(Error::Sieve(
482                    "expected a shift after `@` in sieve".to_string(),
483                ));
484            };
485            self.position += 1;
486            shift
487        } else {
488            0
489        };
490
491        Ok(Node::Residual {
492            modulus,
493            shift: shift % modulus,
494        })
495    }
496}
497
498/// The primes in order from `first_candidate` up: music21's `eratosthenes`.
499///
500/// An incremental sieve: each prime found is filed under its next multiple,
501/// so a candidate that is nobody's multiple is prime and the primes need no
502/// upper bound. The iterator is endless.
503pub fn eratosthenes(first_candidate: u64) -> impl Iterator<Item = u64> {
504    let mut composites: std::collections::HashMap<u64, u64> = std::collections::HashMap::new();
505    let mut candidate: u64 = 2;
506    std::iter::from_fn(move || {
507        loop {
508            let found = candidate;
509            candidate += 1;
510            match composites.remove(&found) {
511                Some(prime) => {
512                    let mut next = found + prime;
513                    while composites.contains_key(&next) {
514                        next += prime;
515                    }
516                    composites.insert(next, prime);
517                }
518                None => {
519                    composites.insert(found * found, found);
520                    if found >= first_candidate {
521                        return Some(found);
522                    }
523                }
524            }
525        }
526    })
527}
528
529/// The witnesses that make Miller-Rabin exact for every number below
530/// 2<sup>64</sup>, so the answer is never a probability.
531const MILLER_RABIN_WITNESSES: [u64; 12] = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37];
532
533fn power_mod(mut base: u64, mut exponent: u64, modulus: u64) -> u64 {
534    let mut result: u64 = 1;
535    base %= modulus;
536    while exponent > 0 {
537        if exponent & 1 == 1 {
538            result = ((u128::from(result) * u128::from(base)) % u128::from(modulus)) as u64;
539        }
540        base = ((u128::from(base) * u128::from(base)) % u128::from(modulus)) as u64;
541        exponent >>= 1;
542    }
543    result
544}
545
546/// Whether a number is prime: music21's `rabinMiller`, answered for the
547/// number's magnitude, so a negative number is as prime as its opposite.
548///
549/// music21 tests random witnesses and answers "probably"; the witnesses
550/// here are the ones that decide every 64-bit number exactly.
551pub fn rabin_miller(n: i64) -> bool {
552    let n = n.unsigned_abs();
553    if n < 2 {
554        return false;
555    }
556    if n < 4 {
557        return true;
558    }
559    if !matches!(n % 6, 1 | 5) {
560        return false;
561    }
562    for witness in MILLER_RABIN_WITNESSES {
563        if n == witness {
564            return true;
565        }
566        if n.is_multiple_of(witness) {
567            return false;
568        }
569    }
570    let (mut odd, mut rounds) = (n - 1, 0);
571    while odd.is_multiple_of(2) {
572        odd /= 2;
573        rounds += 1;
574    }
575    'witnesses: for witness in MILLER_RABIN_WITNESSES {
576        let mut x = power_mod(witness, odd, n);
577        if x == 1 || x == n - 1 {
578            continue;
579        }
580        for _ in 1..rounds {
581            x = ((u128::from(x) * u128::from(x)) % u128::from(n)) as u64;
582            if x == n - 1 {
583                continue 'witnesses;
584            }
585        }
586        return false;
587    }
588    true
589}
590
591/// A set of integers as a run of ones and zeros over its range: music21's
592/// `discreteBinaryPad`, so `[3, 10, 12]` is a one, six noughts, a one, a
593/// nought and a one.
594///
595/// The range is the smallest to the largest member unless `fix_range` gives
596/// another, in which case its own smallest and largest bound the run. An
597/// empty series with no range is an error, since it has no range of its own.
598pub fn discrete_binary_pad(
599    series: &[IntegerType],
600    fix_range: Option<&[IntegerType]>,
601) -> Result<Vec<u8>> {
602    let bounds = fix_range.unwrap_or(series);
603    let (Some(lowest), Some(highest)) = (bounds.iter().min(), bounds.iter().max()) else {
604        return Err(Error::Sieve(
605            "a binary pad needs a range: give a series or a fixRange".to_string(),
606        ));
607    };
608    Ok((*lowest..=*highest)
609        .map(|value| u8::from(series.contains(&value)))
610        .collect())
611}
612
613/// Numbers spaced across the unit interval in proportion to where each
614/// falls between the smallest and the largest: music21's `unitNormRange`,
615/// so `[0, 3, 4]` is `[0, 0.75, 1]`.
616///
617/// `fix_range` bounds the interval with a range other than the series' own.
618/// A series of one number answers nought, and so does every number of a
619/// series with no spread at all.
620pub fn unit_norm_range(series: &[FloatType], fix_range: Option<&[FloatType]>) -> Vec<FloatType> {
621    let bounds = fix_range.unwrap_or(series);
622    let lowest = bounds
623        .iter()
624        .copied()
625        .fold(FloatType::INFINITY, FloatType::min);
626    let highest = bounds
627        .iter()
628        .copied()
629        .fold(FloatType::NEG_INFINITY, FloatType::max);
630    let span = highest - lowest;
631    if series.len() <= 1 {
632        return vec![0.0];
633    }
634    series
635        .iter()
636        .map(|value| {
637            if span == 0.0 {
638                0.0
639            } else {
640                (value - lowest) / span
641            }
642        })
643        .collect()
644}
645
646/// The unit interval cut into `parts` points, nought and one included:
647/// music21's `unitNormEqual`, so three parts are `[0, 0.5, 1]`. One part or
648/// none is a single nought.
649pub fn unit_norm_equal(parts: usize) -> Vec<FloatType> {
650    match parts {
651        0 | 1 => vec![0.0],
652        2 => vec![0.0, 1.0],
653        _ => {
654            let step = 1.0 / (parts - 1) as FloatType;
655            let mut unit: Vec<FloatType> = (0..parts - 1).map(|y| y as FloatType * step).collect();
656            unit.push(1.0);
657            unit
658        }
659    }
660}
661
662/// The values a step of `step` reaches from `a` to `b` inclusive, either as
663/// they are or normalized onto the unit interval: music21's `unitNormStep`.
664/// A range of no width answers nothing; a step of no width cannot cross one
665/// and is an error.
666pub fn unit_norm_step(
667    step: FloatType,
668    a: FloatType,
669    b: FloatType,
670    normalized: bool,
671) -> Result<Vec<FloatType>> {
672    if a == b {
673        return Ok(Vec::new());
674    }
675    if step.is_nan() || step <= 0.0 {
676        return Err(Error::Sieve(format!(
677            "a step of {step} never crosses the range"
678        )));
679    }
680    let (lowest, highest) = if a < b { (a, b) } else { (b, a) };
681    let mut values = Vec::new();
682    let mut x = lowest;
683    while x <= highest {
684        values.push(x);
685        x += step;
686    }
687    Ok(if normalized {
688        unit_norm_equal(values.len())
689    } else {
690        values
691    })
692}
693
694#[cfg(test)]
695mod tests {
696    use super::*;
697
698    /// Every answer here was read off music21's own docstrings.
699    #[test]
700    fn the_number_helpers_answer_what_music21_answers() {
701        assert_eq!(
702            eratosthenes(2).take(5).collect::<Vec<_>>(),
703            [2, 3, 5, 7, 11]
704        );
705        assert_eq!(eratosthenes(95).take(2).collect::<Vec<_>>(), [97, 101]);
706
707        assert!(!rabin_miller(234));
708        assert!(rabin_miller(5));
709        assert!(!rabin_miller(4));
710        assert!(!rabin_miller(97 * 2));
711        assert!(rabin_miller(6_i64.pow(4) + 1));
712        assert!(!rabin_miller(123_986_234_193));
713        assert!(rabin_miller(-7));
714        assert!(!rabin_miller(1));
715        assert!(rabin_miller(1_000_000_007));
716        assert!(!rabin_miller(1_000_000_007 * 3));
717
718        assert_eq!(
719            discrete_binary_pad(&[3, 10, 12], None).unwrap(),
720            [1, 0, 0, 0, 0, 0, 0, 1, 0, 1]
721        );
722        assert_eq!(discrete_binary_pad(&[3, 4, 5], None).unwrap(), [1, 1, 1]);
723        assert_eq!(
724            discrete_binary_pad(&[4], Some(&[2, 5])).unwrap(),
725            [0, 0, 1, 0]
726        );
727        assert!(discrete_binary_pad(&[], None).is_err());
728
729        assert_eq!(unit_norm_range(&[0.0, 3.0, 4.0], None), [0.0, 0.75, 1.0]);
730        let thirds = unit_norm_range(&[1.0, 3.0, 4.0], None);
731        assert!((thirds[1] - 2.0 / 3.0).abs() < 1e-12);
732        assert_eq!(unit_norm_range(&[5.0], None), [0.0]);
733        assert_eq!(unit_norm_range(&[2.0, 2.0], None), [0.0, 0.0]);
734        assert_eq!(unit_norm_range(&[1.0, 2.0], Some(&[0.0, 4.0])), [0.25, 0.5]);
735
736        assert_eq!(unit_norm_equal(3), [0.0, 0.5, 1.0]);
737        assert_eq!(unit_norm_equal(1), [0.0]);
738        assert_eq!(unit_norm_equal(2), [0.0, 1.0]);
739
740        assert_eq!(
741            unit_norm_step(0.5, 0.0, 1.0, true).unwrap(),
742            [0.0, 0.5, 1.0]
743        );
744        assert_eq!(
745            unit_norm_step(0.5, -1.0, 1.0, true).unwrap(),
746            [0.0, 0.25, 0.5, 0.75, 1.0]
747        );
748        assert_eq!(
749            unit_norm_step(0.5, -1.0, 1.0, false).unwrap(),
750            [-1.0, -0.5, 0.0, 0.5, 1.0]
751        );
752        assert_eq!(unit_norm_step(0.25, 0.0, 20.0, true).unwrap().len(), 81);
753        assert_eq!(unit_norm_step(0.25, 0.0, 20.0, false).unwrap().len(), 81);
754        assert!(unit_norm_step(0.5, 1.0, 1.0, true).unwrap().is_empty());
755        assert!(unit_norm_step(0.0, 0.0, 1.0, true).is_err());
756    }
757
758    /// music21 writes a sieve back out as it was given, with the residuals
759    /// normalized and the groups kept. Every string here was read off
760    /// music21 11.0.0b9.
761    #[test]
762    fn a_sieve_is_written_the_way_music21_writes_one() {
763        for (written, expected) in [
764            ("3@11", "3@2"),
765            ("2&4&8|5", "2@0&4@0&8@0|5@0"),
766            ("(5|2)&4&8", "{5@0|2@0}&4@0&8@0"),
767            ("3@2|7@1", "3@2|7@1"),
768        ] {
769            let sieve = Sieve::parse(written).expect("the expression parses");
770            assert_eq!(sieve.to_string(), expected, "writing {written}");
771        }
772    }
773
774    /// music21's `a & b` puts the right operand first and braces both sides.
775    #[test]
776    fn combining_two_sieves_braces_each_side() {
777        let a = Sieve::parse("3@11").expect("a parses");
778        let b = Sieve::parse("2&4&8|5").expect("b parses");
779        assert_eq!(b.intersection(&a).to_string(), "{2@0&4@0&8@0|5@0}&{3@2}");
780        assert_eq!(b.union(&a).to_string(), "{2@0&4@0&8@0|5@0}|{3@2}");
781        assert_eq!(
782            b.symmetric_difference(&a).to_string(),
783            "{2@0&4@0&8@0|5@0}^{3@2}"
784        );
785    }
786
787    #[test]
788    fn collecting_reads_the_sieve_shifted_on_from_a_starting_point() {
789        let sieve = Sieve::parse("3@11").expect("the expression parses");
790        assert_eq!(
791            sieve.collect(10, 100, 10).expect("ten members are found"),
792            [102, 105, 108, 111, 114, 117, 120, 123, 126, 129]
793        );
794    }
795
796    #[test]
797    fn the_segment_formats_answer_what_music21_answers() {
798        let sieve = Sieve::parse("3@2|7@1").expect("the expression parses");
799        assert_eq!(
800            sieve.segment_binary(0, 99)[..12],
801            [0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]
802        );
803        assert_eq!(
804            sieve.segment_widths(0, 99)[..12],
805            [1, 3, 3, 3, 3, 1, 2, 3, 2, 1, 3, 3]
806        );
807        let unit = sieve.segment_unit(0, 99);
808        assert_eq!(unit.len(), 43);
809        assert_eq!(unit[0], 1.0 / 99.0);
810        assert_eq!(*unit.last().expect("the segment is not empty"), 1.0);
811    }
812
813    fn widths(expression: &str) -> Vec<IntegerType> {
814        Sieve::parse(expression)
815            .expect("sieve parses")
816            .interval_widths()
817            .expect("sieve has intervals")
818    }
819
820    #[test]
821    fn a_single_residual_class_cycles_at_its_modulus() {
822        assert_eq!(widths("3@0"), [3]);
823        assert_eq!(widths("4@0"), [4]);
824        assert_eq!(widths("2@0"), [2]);
825        assert_eq!(widths("12@0"), [12]);
826        // A bare modulus is a shift of zero.
827        assert_eq!(widths("5"), [5]);
828    }
829
830    #[test]
831    fn the_major_scale_is_a_sieve() {
832        assert_eq!(
833            widths("(-3@2 & 4) | (-3@1 & 4@1) | (3@2 & 4@2) | (-3 & 4@3)"),
834            [2, 2, 1, 2, 2, 2, 1]
835        );
836    }
837
838    #[test]
839    fn union_intersection_and_symmetric_difference_match_music21() {
840        assert_eq!(widths("3@0|7@0"), [3, 3, 1, 2, 3, 2, 1, 3, 3]);
841        assert_eq!(widths("{3@0|4@0}"), [3, 1, 2, 2, 1, 3]);
842        assert_eq!(widths("3@0&4@0"), [12]);
843        assert_eq!(widths("3@0^4@0"), [1, 2, 2, 1]);
844        assert_eq!(widths("5@2|7@3"), [1, 4, 3, 2, 5, 5, 2, 3, 4, 1]);
845    }
846
847    #[test]
848    fn negation_applies_to_residuals_and_to_groups() {
849        assert_eq!(widths("-3@0"), [1]);
850        assert_eq!(widths("-5@2"), [1, 2, 1, 1]);
851        assert_eq!(widths("-{3@0|4@0}"), [1, 3, 2, 3, 1]);
852    }
853
854    #[test]
855    fn and_binds_tighter_than_or() {
856        // music21 parses 3@0|4@0&6@0 as 3@0|{4@0&6@0}.
857        assert_eq!(widths("3@0|4@0&6@0"), widths("3@0|{4@0&6@0}"));
858        assert_eq!(widths("3@0|4@0&6@0"), [3, 3, 3, 3]);
859        assert_ne!(widths("3@0|4@0&6@0"), widths("{3@0|4@0}&6@0"));
860        assert_eq!(widths("{3@0|4@0}&6@0"), [6, 6]);
861    }
862
863    #[test]
864    fn parentheses_and_braces_group_alike() {
865        assert_eq!(widths("(3@0|4@0)"), widths("{3@0|4@0}"));
866    }
867
868    #[test]
869    fn period_is_the_lcm_of_the_moduli() {
870        assert_eq!(Sieve::parse("3@0").unwrap().period(), 3);
871        assert_eq!(Sieve::parse("3@0|7@0").unwrap().period(), 21);
872        assert_eq!(Sieve::parse("5@2|7@3").unwrap().period(), 35);
873        assert_eq!(Sieve::parse("3@0|4@0").unwrap().period(), 12);
874    }
875
876    #[test]
877    fn a_sieve_too_sparse_for_intervals_errors() {
878        // music21 raises "interval segment has no values" for this: 3@1 has
879        // only the member 1 in 0..=3.
880        assert!(Sieve::parse("3@1").unwrap().interval_widths().is_err());
881    }
882
883    #[test]
884    fn malformed_expressions_error_instead_of_panicking() {
885        for bad in [
886            "", "  ", "@", "3@", "|3@0", "3@0|", "(3@0", "3@0)", "0@0", "3@0 & ", "x", "3@@0",
887        ] {
888            let parsed = Sieve::parse(bad);
889            assert!(parsed.is_err(), "{bad:?} should be rejected");
890        }
891    }
892
893    #[test]
894    fn membership_wraps_for_negative_integers() {
895        let sieve = Sieve::parse("3@1").unwrap();
896        assert!(sieve.contains(1));
897        assert!(sieve.contains(4));
898        assert!(sieve.contains(-2));
899        assert!(!sieve.contains(0));
900    }
901}