Skip to main content

oximo_core/
set.rs

1use std::marker::PhantomData;
2use std::ops::Mul;
3
4use num_traits::PrimInt;
5use rayon::prelude::*;
6use smol_str::SmolStr;
7
8// Note: I used `Box<[IndexKey]>` over:
9//   - `Vec<IndexKey>`: saves one word
10//     (no capacity field) since tuples are never mutated after construction.
11//
12//   - `SmallVec<[IndexKey; N]>` is rejected by the compiler: recursive size
13//      cycle
14
15/// Heap-allocated tuple key payload. Immutable after construction.
16pub type IndexTuple = Box<[IndexKey]>;
17
18/// Runtime representation of a [`Set`]. The key type is tracked only at the type
19/// level (via [`Set`]'s phantom parameter), so the stored representation is
20/// identical for every `K` and carries no per-key type information.
21#[derive(Clone, Debug)]
22enum SetRepr {
23    Range(Vec<i64>),
24    Strings(Vec<SmolStr>),
25    Tuples(Vec<IndexTuple>),
26}
27
28impl SetRepr {
29    fn len(&self) -> usize {
30        match self {
31            Self::Range(v) => v.len(),
32            Self::Strings(v) => v.len(),
33            Self::Tuples(v) => v.len(),
34        }
35    }
36}
37
38/// A single contiguous integer axis of a dense index grid.
39/// Carried by [`Set`] (see its internal `axes`) so an [`crate::IndexedVar`]
40/// built over a range can store its scalars densely and map
41/// a key to a flat offset without hashing.
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43pub struct Axis {
44    pub start: i64,
45    pub len: usize,
46}
47
48/// A finite, ordered index set, parameterized by the type `K` its keys decode to.
49///
50/// `K` is a phantom marker: the runtime representation is the same erased
51/// payload regardless of `K`, so carrying the key type costs nothing at runtime.
52/// It exists so the `variable!`/`constraint!`/`sum!` macros can infer the
53/// closure parameter type from the set instead of requiring an annotation.
54///
55/// Supports integer ranges (`K = usize`), string lists (`K = String`), and
56/// arbitrary tuple lists (built via [`Set::product`] / the `&a * &b` operator.
57///
58/// `axes` is `Some` exactly when the set is a dense integer grid
59/// and records the per-axis extents.
60/// It is `None` for string sets, sparse/`from_ints` sets, and any
61/// `filter`ed set.
62pub struct Set<K = IndexKey> {
63    repr: SetRepr,
64    axes: Option<Box<[Axis]>>,
65    _k: PhantomData<fn() -> K>,
66}
67
68// Manual `Clone`/`Debug` so they hold for every `K`
69impl<K> Clone for Set<K> {
70    fn clone(&self) -> Self {
71        Self { repr: self.repr.clone(), axes: self.axes.clone(), _k: PhantomData }
72    }
73}
74
75impl<K> std::fmt::Debug for Set<K> {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        std::fmt::Debug::fmt(&self.repr, f)
78    }
79}
80
81impl<K> Set<K> {
82    fn from_repr(repr: SetRepr) -> Self {
83        Self { repr, axes: None, _k: PhantomData }
84    }
85
86    fn from_repr_with_axes(repr: SetRepr, axes: Box<[Axis]>) -> Self {
87        Self { repr, axes: Some(axes), _k: PhantomData }
88    }
89
90    /// Per-axis extents when this set is a dense integer grid (a range or a
91    /// product of ranges), else `None`. Read by the `IndexedVar` builder to pick
92    /// dense vs sparse storage.
93    pub(crate) fn axes(&self) -> Option<&[Axis]> {
94        self.axes.as_deref()
95    }
96
97    /// Build a tuple set directly from an iterator of keys.
98    pub fn tuples<I, T>(iter: I) -> Self
99    where
100        I: IntoIterator<Item = T>,
101        T: Into<IndexTuple>,
102    {
103        Self::from_repr(SetRepr::Tuples(iter.into_iter().map(Into::into).collect()))
104    }
105
106    /// Filter keys with a predicate. Preserves the original variant where
107    /// possible.
108    #[must_use]
109    pub fn filter<F>(&self, mut f: F) -> Self
110    where
111        F: FnMut(&IndexKey) -> bool,
112    {
113        let repr = match &self.repr {
114            SetRepr::Range(v) => {
115                SetRepr::Range(v.iter().copied().filter(|i| f(&IndexKey::Int(*i))).collect())
116            }
117            SetRepr::Strings(v) => SetRepr::Strings(
118                v.iter()
119                    .filter_map(|s| {
120                        let key = IndexKey::Str(s.clone());
121                        f(&key).then(|| s.clone())
122                    })
123                    .collect(),
124            ),
125            SetRepr::Tuples(v) => SetRepr::Tuples(
126                v.iter()
127                    .filter_map(|t| {
128                        let key = IndexKey::Tuple(t.clone());
129                        f(&key).then(|| match key {
130                            IndexKey::Tuple(owned) => owned,
131                            _ => unreachable!(),
132                        })
133                    })
134                    .collect(),
135            ),
136        };
137        Self::from_repr(repr)
138    }
139
140    /// Filter keys with a predicate over the typed, by-value decoded key.
141    ///
142    /// Unlike [`Self::filter`] (which hands the closure a raw [`IndexKey`]), the
143    /// key is decoded to `K` first, so a product set yields native tuples and no
144    /// manual `as_tuple().unwrap()` unpacking is needed. The receiver's `K` pins
145    /// the closure parameter, so it usually needs no annotation.
146    ///
147    /// ```
148    /// use oximo_core::Set;
149    /// let plants = Set::strings(["seattle", "san-diego"]);
150    /// // No-self-loop arcs; keys decoded to `(String, String)`.
151    /// let arcs = (&plants * &plants).filter_typed(|(p, q)| p != q);
152    /// assert_eq!(arcs.len(), 2);
153    /// ```
154    #[must_use]
155    pub fn filter_typed<F>(&self, mut pred: F) -> Self
156    where
157        K: FromIndexKey,
158        F: FnMut(K) -> bool,
159    {
160        self.filter(|k| pred(K::from_index_key(k)))
161    }
162
163    pub fn len(&self) -> usize {
164        self.repr.len()
165    }
166
167    pub fn is_empty(&self) -> bool {
168        self.len() == 0
169    }
170
171    /// Whether the backing representation is the integer-range variant.
172    pub fn is_range(&self) -> bool {
173        matches!(self.repr, SetRepr::Range(_))
174    }
175
176    /// Whether the backing representation is the string-list variant.
177    pub fn is_strings(&self) -> bool {
178        matches!(self.repr, SetRepr::Strings(_))
179    }
180
181    /// Whether the backing representation is the tuple-list variant.
182    pub fn is_tuples(&self) -> bool {
183        matches!(self.repr, SetRepr::Tuples(_))
184    }
185
186    /// Cartesian product of two sets. Inner tuple keys are flattened so a
187    /// product `(a * b) * c` yields 3-element tuples, not nested 2-tuples.
188    ///
189    /// # Panics
190    /// Panics if `a.len() * b.len()` overflows `usize`.
191    #[must_use]
192    pub fn product<B>(a: &Set<K>, b: &Set<B>) -> Set<<K as KeyCat<B>>::Out>
193    where
194        K: KeyCat<B>,
195    {
196        let a_len = a.len();
197        let b_len = b.len();
198        let total = a_len.checked_mul(b_len).expect("Set::product size overflow");
199
200        let axes = match (a.axes(), b.axes()) {
201            (Some(aa), Some(bb)) => {
202                let mut v = Vec::with_capacity(aa.len() + bb.len());
203                v.extend_from_slice(aa);
204                v.extend_from_slice(bb);
205                Some(v.into_boxed_slice())
206            }
207            _ => None,
208        };
209
210        // Below this size, rayon dispatch overhead may dominate, so we stay serial.
211        // TODO: benchmark and tune this threshold.
212        const PAR_THRESHOLD: usize = 4096;
213        let out: Vec<IndexTuple> = if total < PAR_THRESHOLD {
214            let mut out = Vec::with_capacity(total);
215            for ka in a {
216                for kb in b {
217                    let mut parts: Vec<IndexKey> = Vec::new();
218                    push_flat(&mut parts, ka.clone());
219                    push_flat(&mut parts, kb);
220                    out.push(parts.into_boxed_slice());
221                }
222            }
223            out
224        } else {
225            let a_keys: Vec<IndexKey> = a.iter().collect();
226            let b_keys: Vec<IndexKey> = b.iter().collect();
227            (0..total)
228                .into_par_iter()
229                .map(|i| {
230                    let mut parts: Vec<IndexKey> = Vec::new();
231                    push_flat(&mut parts, a_keys[i / b_len].clone());
232                    push_flat(&mut parts, b_keys[i % b_len].clone());
233                    parts.into_boxed_slice()
234                })
235                .collect()
236        };
237
238        match axes {
239            Some(axes) => Set::from_repr_with_axes(SetRepr::Tuples(out), axes),
240            None => Set::from_repr(SetRepr::Tuples(out)),
241        }
242    }
243}
244
245impl Set<usize> {
246    /// Build an integer index set from a range over any primitive integer
247    /// type. Accepts `Range<i64>`, `Range<i32>`, `Range<usize>`, etc.
248    ///
249    /// The keys decode to `usize`.
250    /// Negative elements are accepted into the payload but panic when
251    /// decoded to `usize`.
252    ///
253    /// # Panics
254    /// Panics if either range bound does not fit in `i64`.
255    #[must_use]
256    pub fn range<T: PrimInt>(r: std::ops::Range<T>) -> Self {
257        let start = r.start.to_i64().expect("range start out of i64 range");
258        let end = r.end.to_i64().expect("range end out of i64 range");
259        Self::dense_i64(start, end)
260    }
261
262    /// Build a dense contiguous integer set from an `i64` half-open range,
263    /// recording the single axis so the resulting [`IndexedVar`] stores densely.
264    /// Shared by [`Self::range`] and the `RangeInclusive` `IntoSet` path.
265    pub(crate) fn dense_i64(start: i64, end: i64) -> Self {
266        let vals: Vec<i64> = (start..end).collect();
267        let len = vals.len();
268        Self::from_repr_with_axes(SetRepr::Range(vals), Box::from([Axis { start, len }]))
269    }
270
271    /// Build an integer index set from an iterator of any primitive integer
272    /// type. Useful when keys are sparse or computed.
273    ///
274    /// # Panics
275    /// Panics if any element does not fit in `i64`.
276    pub fn from_ints<T, I>(iter: I) -> Self
277    where
278        T: PrimInt,
279        I: IntoIterator<Item = T>,
280    {
281        Self::from_repr(SetRepr::Range(
282            iter.into_iter().map(|v| v.to_i64().expect("element out of i64 range")).collect(),
283        ))
284    }
285}
286
287impl Set<String> {
288    pub fn strings<I, S>(iter: I) -> Self
289    where
290        I: IntoIterator<Item = S>,
291        S: Into<SmolStr>,
292    {
293        Self::from_repr(SetRepr::Strings(iter.into_iter().map(Into::into).collect()))
294    }
295}
296
297fn push_flat(dst: &mut Vec<IndexKey>, k: IndexKey) {
298    match k {
299        IndexKey::Tuple(inner) => dst.extend(inner.into_vec()),
300        other => dst.push(other),
301    }
302}
303
304fn make_tuple<I: IntoIterator<Item = IndexKey>>(items: I) -> IndexTuple {
305    let mut v: Vec<IndexKey> = Vec::new();
306    for k in items {
307        push_flat(&mut v, k);
308    }
309    v.into_boxed_slice()
310}
311
312impl<A, B> Mul<&Set<B>> for &Set<A>
313where
314    A: KeyCat<B>,
315{
316    type Output = Set<<A as KeyCat<B>>::Out>;
317    fn mul(self, rhs: &Set<B>) -> Self::Output {
318        Set::product(self, rhs)
319    }
320}
321
322/// Type-level concatenation of index key types, mirroring the runtime tuple
323/// flattening in [`Set::product`]. The arity ceiling is 4, matching the
324/// [`FromIndexKey`]/`From<(...)>` tuple implementations.
325#[diagnostic::on_unimplemented(
326    message = "cannot form a Cartesian product index key from `{Self}` and `{Rhs}`",
327    label = "no product key for `{Self}` * `{Rhs}`",
328    note = "`&a * &b` composes scalar keys (`usize`/`i64`/`i32`/`String`) into flat tuples up to arity 4. A 5th axis or a non-scalar operand is unsupported"
329)]
330pub trait KeyCat<Rhs> {
331    type Out;
332}
333
334/// Marker for non-tuple ("scalar") index key types. Lets [`KeyCat`] distinguish
335/// the scalar base case from the tuple-extension cases without overlap.
336pub trait ScalarKey {}
337impl ScalarKey for usize {}
338impl ScalarKey for i32 {}
339impl ScalarKey for i64 {}
340impl ScalarKey for String {}
341impl ScalarKey for IndexKey {}
342
343impl<A: ScalarKey, B: ScalarKey> KeyCat<B> for A {
344    type Out = (A, B);
345}
346
347impl<A, B, C: ScalarKey> KeyCat<C> for (A, B) {
348    type Out = (A, B, C);
349}
350
351impl<A, B, C, D: ScalarKey> KeyCat<D> for (A, B, C) {
352    type Out = (A, B, C, D);
353}
354
355// Right-associated / tuple-on-the-right products (`a * (b * c)`,
356// `(a * b) * (c * d)`). The macros only ever left-fold with a scalar right
357// operand, but `Set::product` flattens both sides, so we keep manual
358// products associative up to the arity-4 ceiling.
359impl<A: ScalarKey, B, C> KeyCat<(B, C)> for A {
360    type Out = (A, B, C);
361}
362
363impl<A: ScalarKey, B, C, D> KeyCat<(B, C, D)> for A {
364    type Out = (A, B, C, D);
365}
366
367impl<A, B, C, D> KeyCat<(C, D)> for (A, B) {
368    type Out = (A, B, C, D);
369}
370
371/// A serializable index key from a [`Set`].
372#[derive(Clone, Debug, PartialEq, Eq, Hash)]
373pub enum IndexKey {
374    Int(i64),
375    Str(SmolStr),
376    Tuple(IndexTuple),
377}
378
379impl IndexKey {
380    /// Build a tuple key from any iterable of convertible items. Nested tuple
381    /// keys are flattened.
382    pub fn tuple<I, T>(iter: I) -> Self
383    where
384        I: IntoIterator<Item = T>,
385        T: Into<IndexKey>,
386    {
387        Self::Tuple(make_tuple(iter.into_iter().map(Into::into)))
388    }
389
390    pub fn as_i64(&self) -> Option<i64> {
391        if let Self::Int(v) = self { Some(*v) } else { None }
392    }
393
394    pub fn as_str(&self) -> Option<&str> {
395        if let Self::Str(s) = self { Some(s.as_str()) } else { None }
396    }
397
398    pub fn as_tuple(&self) -> Option<&[IndexKey]> {
399        if let Self::Tuple(t) = self { Some(&t[..]) } else { None }
400    }
401}
402
403impl From<i64> for IndexKey {
404    fn from(v: i64) -> Self {
405        Self::Int(v)
406    }
407}
408
409impl From<i32> for IndexKey {
410    fn from(v: i32) -> Self {
411        Self::Int(i64::from(v))
412    }
413}
414
415impl From<usize> for IndexKey {
416    fn from(v: usize) -> Self {
417        Self::Int(i64::try_from(v).expect("usize -> i64 overflow"))
418    }
419}
420
421impl From<&str> for IndexKey {
422    fn from(s: &str) -> Self {
423        Self::Str(SmolStr::new(s))
424    }
425}
426
427impl From<String> for IndexKey {
428    fn from(s: String) -> Self {
429        Self::Str(SmolStr::from(s))
430    }
431}
432
433impl From<&String> for IndexKey {
434    fn from(s: &String) -> Self {
435        Self::Str(SmolStr::new(s.as_str()))
436    }
437}
438
439// Reference conversions.
440impl From<&usize> for IndexKey {
441    fn from(v: &usize) -> Self {
442        Self::from(*v)
443    }
444}
445
446impl From<&i64> for IndexKey {
447    fn from(v: &i64) -> Self {
448        Self::Int(*v)
449    }
450}
451
452impl From<&i32> for IndexKey {
453    fn from(v: &i32) -> Self {
454        Self::Int(i64::from(*v))
455    }
456}
457
458impl From<&&str> for IndexKey {
459    fn from(s: &&str) -> Self {
460        Self::Str(SmolStr::new(*s))
461    }
462}
463
464impl From<&&String> for IndexKey {
465    fn from(s: &&String) -> Self {
466        Self::Str(SmolStr::new(s.as_str()))
467    }
468}
469
470impl<A, B> From<(A, B)> for IndexKey
471where
472    A: Into<IndexKey>,
473    B: Into<IndexKey>,
474{
475    fn from(t: (A, B)) -> Self {
476        Self::Tuple(make_tuple([t.0.into(), t.1.into()]))
477    }
478}
479
480impl<A, B, C> From<(A, B, C)> for IndexKey
481where
482    A: Into<IndexKey>,
483    B: Into<IndexKey>,
484    C: Into<IndexKey>,
485{
486    fn from(t: (A, B, C)) -> Self {
487        Self::Tuple(make_tuple([t.0.into(), t.1.into(), t.2.into()]))
488    }
489}
490
491impl<A, B, C, D> From<(A, B, C, D)> for IndexKey
492where
493    A: Into<IndexKey>,
494    B: Into<IndexKey>,
495    C: Into<IndexKey>,
496    D: Into<IndexKey>,
497{
498    fn from(t: (A, B, C, D)) -> Self {
499        Self::Tuple(make_tuple([t.0.into(), t.1.into(), t.2.into(), t.3.into()]))
500    }
501}
502
503/// Typed projection of an [`IndexKey`]. Implementations panic when the
504/// key's shape does not match the target type, the same contract as
505/// [`crate::indexed::IndexedVar`] indexing on a missing key.
506///
507/// Used by the indexed-family `constraint!` macro (and similar rule helpers) to
508/// give the closure typed indices directly:
509///
510/// ```ignore
511/// constraint!(m, supply[(p, m) in &plants * &markets], {
512///     // p, m are native String, no manual unpack
513///     ...
514/// });
515/// ```
516#[diagnostic::on_unimplemented(
517    message = "`{Self}` is not a valid index key type",
518    label = "cannot be decoded from an index key",
519    note = "index keys decode to `usize`, `i64`, `i32`, `String`, `IndexKey`, or a tuple of those up to arity 4",
520    note = "annotate the binding to one of these (e.g. `for k: usize in set`) or match the `Set`'s key type"
521)]
522pub trait FromIndexKey: Sized {
523    fn from_index_key(k: &IndexKey) -> Self;
524}
525
526impl FromIndexKey for IndexKey {
527    fn from_index_key(k: &IndexKey) -> Self {
528        k.clone()
529    }
530}
531
532impl FromIndexKey for i64 {
533    fn from_index_key(k: &IndexKey) -> Self {
534        k.as_i64().unwrap_or_else(|| panic!("expected Int key, got {k:?}"))
535    }
536}
537
538impl FromIndexKey for i32 {
539    fn from_index_key(k: &IndexKey) -> Self {
540        let v = i64::from_index_key(k);
541        i32::try_from(v).unwrap_or_else(|_| panic!("key {v} out of i32 range"))
542    }
543}
544
545impl FromIndexKey for usize {
546    fn from_index_key(k: &IndexKey) -> Self {
547        let v = i64::from_index_key(k);
548        usize::try_from(v).unwrap_or_else(|_| panic!("key {v} out of usize range"))
549    }
550}
551
552impl FromIndexKey for String {
553    fn from_index_key(k: &IndexKey) -> Self {
554        k.as_str().unwrap_or_else(|| panic!("expected Str key, got {k:?}")).to_owned()
555    }
556}
557
558fn tuple_parts<'a>(k: &'a IndexKey, expected: usize) -> &'a [IndexKey] {
559    let p = k.as_tuple().unwrap_or_else(|| panic!("expected Tuple key, got {k:?}"));
560    assert_eq!(p.len(), expected, "expected tuple of arity {expected}, got arity {}", p.len());
561    p
562}
563
564impl<A, B> FromIndexKey for (A, B)
565where
566    A: FromIndexKey,
567    B: FromIndexKey,
568{
569    fn from_index_key(k: &IndexKey) -> Self {
570        let p = tuple_parts(k, 2);
571        (A::from_index_key(&p[0]), B::from_index_key(&p[1]))
572    }
573}
574
575impl<A, B, C> FromIndexKey for (A, B, C)
576where
577    A: FromIndexKey,
578    B: FromIndexKey,
579    C: FromIndexKey,
580{
581    fn from_index_key(k: &IndexKey) -> Self {
582        let p = tuple_parts(k, 3);
583        (A::from_index_key(&p[0]), B::from_index_key(&p[1]), C::from_index_key(&p[2]))
584    }
585}
586
587impl<A, B, C, D> FromIndexKey for (A, B, C, D)
588where
589    A: FromIndexKey,
590    B: FromIndexKey,
591    C: FromIndexKey,
592    D: FromIndexKey,
593{
594    fn from_index_key(k: &IndexKey) -> Self {
595        let p = tuple_parts(k, 4);
596        (
597            A::from_index_key(&p[0]),
598            B::from_index_key(&p[1]),
599            C::from_index_key(&p[2]),
600            D::from_index_key(&p[3]),
601        )
602    }
603}
604
605impl<'a, K> IntoIterator for &'a Set<K> {
606    type Item = IndexKey;
607    type IntoIter = SetIter<'a>;
608    fn into_iter(self) -> Self::IntoIter {
609        self.iter()
610    }
611}
612
613impl<K> Set<K> {
614    pub fn iter(&self) -> SetIter<'_> {
615        SetIter { repr: &self.repr, pos: 0 }
616    }
617
618    pub fn par_iter(&self) -> impl ParallelIterator<Item = IndexKey> + '_ {
619        match &self.repr {
620            SetRepr::Range(v) => v.par_iter().map(|i| IndexKey::Int(*i)).collect::<Vec<_>>(),
621            SetRepr::Strings(v) => {
622                v.par_iter().map(|s| IndexKey::Str(s.clone())).collect::<Vec<_>>()
623            }
624            SetRepr::Tuples(v) => {
625                v.par_iter().map(|t| IndexKey::Tuple(t.clone())).collect::<Vec<_>>()
626            }
627        }
628        .into_par_iter()
629    }
630}
631
632#[derive(Debug)]
633pub struct SetIter<'a> {
634    repr: &'a SetRepr,
635    pos: usize,
636}
637
638impl<'a> Iterator for SetIter<'a> {
639    type Item = IndexKey;
640    fn next(&mut self) -> Option<Self::Item> {
641        let out = match self.repr {
642            SetRepr::Range(v) => v.get(self.pos).copied().map(IndexKey::Int),
643            SetRepr::Strings(v) => v.get(self.pos).cloned().map(IndexKey::Str),
644            SetRepr::Tuples(v) => v.get(self.pos).cloned().map(IndexKey::Tuple),
645        };
646        if out.is_some() {
647            self.pos += 1;
648        }
649        out
650    }
651}