Skip to main content

oximo_core/
set.rs

1use std::ops::Mul;
2
3use num_traits::PrimInt;
4use rayon::prelude::*;
5use smol_str::SmolStr;
6
7// Note: I used `Box<[IndexKey]>` over:
8//   - `Vec<IndexKey>`: saves one word
9//     (no capacity field) since tuples are never mutated after construction.
10//
11//   - `SmallVec<[IndexKey; N]>` is rejected by the compiler: recursive size
12//      cycle
13
14/// Heap-allocated tuple key payload. Immutable after construction.
15pub type IndexTuple = Box<[IndexKey]>;
16
17/// A finite, ordered index set.
18///
19/// Supports integer ranges, string lists, and arbitrary tuple lists (built via
20/// [`Set::product`] / the `&a * &b` operator, or constructed directly).
21#[derive(Clone, Debug)]
22pub enum Set {
23    Range(Vec<i64>),
24    Strings(Vec<SmolStr>),
25    Tuples(Vec<IndexTuple>),
26}
27
28impl Set {
29    /// Build an integer index set from a range over any primitive integer
30    /// type. Accepts `Range<i64>`, `Range<i32>`, `Range<usize>`, etc.
31    ///
32    /// The untyped literal form `Set::range(0..5)` defaults to `i32` and
33    /// works without annotation.
34    ///
35    /// # Panics
36    /// Panics if either range bound does not fit in `i64`.
37    pub fn range<T: PrimInt>(r: std::ops::Range<T>) -> Self {
38        let start = r.start.to_i64().expect("range start out of i64 range");
39        let end = r.end.to_i64().expect("range end out of i64 range");
40        Self::Range((start..end).collect())
41    }
42
43    /// Build an integer index set from an iterator of any primitive integer
44    /// type. Useful when keys are sparse or computed (e.g. only even indices).
45    ///
46    /// # Panics
47    /// Panics if any element does not fit in `i64`.
48    pub fn from_ints<T, I>(iter: I) -> Self
49    where
50        T: PrimInt,
51        I: IntoIterator<Item = T>,
52    {
53        Self::Range(
54            iter.into_iter().map(|v| v.to_i64().expect("element out of i64 range")).collect(),
55        )
56    }
57
58    pub fn strings<I, S>(iter: I) -> Self
59    where
60        I: IntoIterator<Item = S>,
61        S: Into<SmolStr>,
62    {
63        Self::Strings(iter.into_iter().map(Into::into).collect())
64    }
65
66    /// Build a tuple set directly from an iterator of keys.
67    pub fn tuples<I, T>(iter: I) -> Self
68    where
69        I: IntoIterator<Item = T>,
70        T: Into<IndexTuple>,
71    {
72        Self::Tuples(iter.into_iter().map(Into::into).collect())
73    }
74
75    /// Cartesian product of two sets. Inner tuple keys are flattened so a
76    /// product `(a * b) * c` yields 3-element tuples, not nested 2-tuples.
77    ///
78    /// # Panics
79    /// Panics if `a.len() * b.len()` overflows `usize`.
80    pub fn product(a: &Set, b: &Set) -> Self {
81        let a_len = a.len();
82        let b_len = b.len();
83        let total = a_len.checked_mul(b_len).expect("Set::product size overflow");
84
85        // Below this size, rayon dispatch overhead may dominate, so we stay serial.
86        // TODO: benchmark and tune this threshold.
87        const PAR_THRESHOLD: usize = 4096;
88        if total < PAR_THRESHOLD {
89            let mut out = Vec::with_capacity(total);
90            for ka in a {
91                for kb in b {
92                    let mut parts: Vec<IndexKey> = Vec::new();
93                    push_flat(&mut parts, ka.clone());
94                    push_flat(&mut parts, kb);
95                    out.push(parts.into_boxed_slice());
96                }
97            }
98            return Self::Tuples(out);
99        }
100
101        let a_keys: Vec<IndexKey> = a.iter().collect();
102        let b_keys: Vec<IndexKey> = b.iter().collect();
103        let out: Vec<IndexTuple> = (0..total)
104            .into_par_iter()
105            .map(|i| {
106                let mut parts: Vec<IndexKey> = Vec::new();
107                push_flat(&mut parts, a_keys[i / b_len].clone());
108                push_flat(&mut parts, b_keys[i % b_len].clone());
109                parts.into_boxed_slice()
110            })
111            .collect();
112        Self::Tuples(out)
113    }
114
115    /// Filter keys with a predicate. Preserves the original variant where
116    /// possible, filtered `Range`/`Strings` stay in their native variant.
117    pub fn filter<F>(&self, mut f: F) -> Self
118    where
119        F: FnMut(&IndexKey) -> bool,
120    {
121        match self {
122            Self::Range(v) => {
123                Self::Range(v.iter().copied().filter(|i| f(&IndexKey::Int(*i))).collect())
124            }
125            Self::Strings(v) => Self::Strings(
126                v.iter()
127                    .filter_map(|s| {
128                        let key = IndexKey::Str(s.clone());
129                        f(&key).then(|| s.clone())
130                    })
131                    .collect(),
132            ),
133            Self::Tuples(v) => Self::Tuples(
134                v.iter()
135                    .filter_map(|t| {
136                        let key = IndexKey::Tuple(t.clone());
137                        f(&key).then(|| match key {
138                            IndexKey::Tuple(owned) => owned,
139                            _ => unreachable!(),
140                        })
141                    })
142                    .collect(),
143            ),
144        }
145    }
146
147    pub fn len(&self) -> usize {
148        match self {
149            Self::Range(v) => v.len(),
150            Self::Strings(v) => v.len(),
151            Self::Tuples(v) => v.len(),
152        }
153    }
154
155    pub fn is_empty(&self) -> bool {
156        self.len() == 0
157    }
158}
159
160fn push_flat(dst: &mut Vec<IndexKey>, k: IndexKey) {
161    match k {
162        IndexKey::Tuple(inner) => dst.extend(inner.into_vec()),
163        other => dst.push(other),
164    }
165}
166
167fn make_tuple<I: IntoIterator<Item = IndexKey>>(items: I) -> IndexTuple {
168    let mut v: Vec<IndexKey> = Vec::new();
169    for k in items {
170        push_flat(&mut v, k);
171    }
172    v.into_boxed_slice()
173}
174
175impl Mul<&Set> for &Set {
176    type Output = Set;
177    fn mul(self, rhs: &Set) -> Set {
178        Set::product(self, rhs)
179    }
180}
181
182/// A serializable index key from a [`Set`].
183#[derive(Clone, Debug, PartialEq, Eq, Hash)]
184pub enum IndexKey {
185    Int(i64),
186    Str(SmolStr),
187    Tuple(IndexTuple),
188}
189
190impl IndexKey {
191    /// Build a tuple key from any iterable of convertible items. Nested tuple
192    /// keys are flattened.
193    pub fn tuple<I, T>(iter: I) -> Self
194    where
195        I: IntoIterator<Item = T>,
196        T: Into<IndexKey>,
197    {
198        Self::Tuple(make_tuple(iter.into_iter().map(Into::into)))
199    }
200
201    pub fn as_i64(&self) -> Option<i64> {
202        if let Self::Int(v) = self { Some(*v) } else { None }
203    }
204
205    pub fn as_str(&self) -> Option<&str> {
206        if let Self::Str(s) = self { Some(s.as_str()) } else { None }
207    }
208
209    pub fn as_tuple(&self) -> Option<&[IndexKey]> {
210        if let Self::Tuple(t) = self { Some(&t[..]) } else { None }
211    }
212}
213
214impl From<i64> for IndexKey {
215    fn from(v: i64) -> Self {
216        Self::Int(v)
217    }
218}
219
220impl From<i32> for IndexKey {
221    fn from(v: i32) -> Self {
222        Self::Int(i64::from(v))
223    }
224}
225
226impl From<usize> for IndexKey {
227    fn from(v: usize) -> Self {
228        Self::Int(i64::try_from(v).expect("usize -> i64 overflow"))
229    }
230}
231
232impl From<&str> for IndexKey {
233    fn from(s: &str) -> Self {
234        Self::Str(SmolStr::new(s))
235    }
236}
237
238impl From<String> for IndexKey {
239    fn from(s: String) -> Self {
240        Self::Str(SmolStr::from(s))
241    }
242}
243
244impl From<&String> for IndexKey {
245    fn from(s: &String) -> Self {
246        Self::Str(SmolStr::new(s.as_str()))
247    }
248}
249
250impl<A, B> From<(A, B)> for IndexKey
251where
252    A: Into<IndexKey>,
253    B: Into<IndexKey>,
254{
255    fn from(t: (A, B)) -> Self {
256        Self::Tuple(make_tuple([t.0.into(), t.1.into()]))
257    }
258}
259
260impl<A, B, C> From<(A, B, C)> for IndexKey
261where
262    A: Into<IndexKey>,
263    B: Into<IndexKey>,
264    C: Into<IndexKey>,
265{
266    fn from(t: (A, B, C)) -> Self {
267        Self::Tuple(make_tuple([t.0.into(), t.1.into(), t.2.into()]))
268    }
269}
270
271impl<A, B, C, D> From<(A, B, C, D)> for IndexKey
272where
273    A: Into<IndexKey>,
274    B: Into<IndexKey>,
275    C: Into<IndexKey>,
276    D: Into<IndexKey>,
277{
278    fn from(t: (A, B, C, D)) -> Self {
279        Self::Tuple(make_tuple([t.0.into(), t.1.into(), t.2.into(), t.3.into()]))
280    }
281}
282
283/// Typed projection of an [`IndexKey`]. Implementations panic when the
284/// key's shape does not match the target type, the same contract as
285/// [`crate::indexed::IndexedVar`] indexing on a missing key.
286///
287/// Used by [`crate::model::Model::add_constraints_over`] (and similar rule
288/// helpers) to give the closure typed indices directly:
289///
290/// ```ignore
291/// m.add_constraints_over("supply", &(&plants * &markets), |(p, m): (String, String)| {
292///     // p, m are native String, no manual unpack
293///     ...
294/// });
295/// ```
296pub trait FromIndexKey: Sized {
297    fn from_index_key(k: &IndexKey) -> Self;
298}
299
300impl FromIndexKey for IndexKey {
301    fn from_index_key(k: &IndexKey) -> Self {
302        k.clone()
303    }
304}
305
306impl FromIndexKey for i64 {
307    fn from_index_key(k: &IndexKey) -> Self {
308        k.as_i64().unwrap_or_else(|| panic!("expected Int key, got {k:?}"))
309    }
310}
311
312impl FromIndexKey for i32 {
313    fn from_index_key(k: &IndexKey) -> Self {
314        let v = i64::from_index_key(k);
315        i32::try_from(v).unwrap_or_else(|_| panic!("key {v} out of i32 range"))
316    }
317}
318
319impl FromIndexKey for usize {
320    fn from_index_key(k: &IndexKey) -> Self {
321        let v = i64::from_index_key(k);
322        usize::try_from(v).unwrap_or_else(|_| panic!("key {v} out of usize range"))
323    }
324}
325
326impl FromIndexKey for String {
327    fn from_index_key(k: &IndexKey) -> Self {
328        k.as_str().unwrap_or_else(|| panic!("expected Str key, got {k:?}")).to_owned()
329    }
330}
331
332fn tuple_parts<'a>(k: &'a IndexKey, expected: usize) -> &'a [IndexKey] {
333    let p = k.as_tuple().unwrap_or_else(|| panic!("expected Tuple key, got {k:?}"));
334    assert_eq!(p.len(), expected, "expected tuple of arity {expected}, got arity {}", p.len());
335    p
336}
337
338impl<A, B> FromIndexKey for (A, B)
339where
340    A: FromIndexKey,
341    B: FromIndexKey,
342{
343    fn from_index_key(k: &IndexKey) -> Self {
344        let p = tuple_parts(k, 2);
345        (A::from_index_key(&p[0]), B::from_index_key(&p[1]))
346    }
347}
348
349impl<A, B, C> FromIndexKey for (A, B, C)
350where
351    A: FromIndexKey,
352    B: FromIndexKey,
353    C: FromIndexKey,
354{
355    fn from_index_key(k: &IndexKey) -> Self {
356        let p = tuple_parts(k, 3);
357        (A::from_index_key(&p[0]), B::from_index_key(&p[1]), C::from_index_key(&p[2]))
358    }
359}
360
361impl<A, B, C, D> FromIndexKey for (A, B, C, D)
362where
363    A: FromIndexKey,
364    B: FromIndexKey,
365    C: FromIndexKey,
366    D: FromIndexKey,
367{
368    fn from_index_key(k: &IndexKey) -> Self {
369        let p = tuple_parts(k, 4);
370        (
371            A::from_index_key(&p[0]),
372            B::from_index_key(&p[1]),
373            C::from_index_key(&p[2]),
374            D::from_index_key(&p[3]),
375        )
376    }
377}
378
379impl<'a> IntoIterator for &'a Set {
380    type Item = IndexKey;
381    type IntoIter = SetIter<'a>;
382    fn into_iter(self) -> Self::IntoIter {
383        self.iter()
384    }
385}
386
387impl Set {
388    pub fn iter(&self) -> SetIter<'_> {
389        SetIter { set: self, pos: 0 }
390    }
391
392    pub fn par_iter(&self) -> impl ParallelIterator<Item = IndexKey> + '_ {
393        match self {
394            Self::Range(v) => v.par_iter().map(|i| IndexKey::Int(*i)).collect::<Vec<_>>(),
395            Self::Strings(v) => v.par_iter().map(|s| IndexKey::Str(s.clone())).collect::<Vec<_>>(),
396            Self::Tuples(v) => v.par_iter().map(|t| IndexKey::Tuple(t.clone())).collect::<Vec<_>>(),
397        }
398        .into_par_iter()
399    }
400}
401
402#[derive(Debug)]
403pub struct SetIter<'a> {
404    set: &'a Set,
405    pos: usize,
406}
407
408impl<'a> Iterator for SetIter<'a> {
409    type Item = IndexKey;
410    fn next(&mut self) -> Option<Self::Item> {
411        let out = match self.set {
412            Set::Range(v) => v.get(self.pos).copied().map(IndexKey::Int),
413            Set::Strings(v) => v.get(self.pos).cloned().map(IndexKey::Str),
414            Set::Tuples(v) => v.get(self.pos).cloned().map(IndexKey::Tuple),
415        };
416        if out.is_some() {
417            self.pos += 1;
418        }
419        out
420    }
421}