pub struct Set<K = IndexKey> { /* private fields */ }Expand description
A finite, ordered index set, parameterized by the type K its keys decode to.
K is a phantom marker: the runtime representation is the same erased
payload regardless of K, so carrying the key type costs nothing at runtime.
It exists so the variable!/constraint!/sum! macros can infer the
closure parameter type from the set instead of requiring an annotation.
Supports integer ranges (K = usize), string lists (K = String), and
arbitrary tuple lists (built via Set::product / the &a * &b operator.
axes is Some exactly when the set is a dense integer grid
and records the per-axis extents.
It is None for string sets, sparse/from_ints sets, and any
filtered set.
Implementations§
Source§impl<K> Set<K>
impl<K> Set<K>
Sourcepub fn filter<F>(&self, f: F) -> Set<K>
pub fn filter<F>(&self, f: F) -> Set<K>
Filter keys with a predicate. Preserves the original variant where possible.
Sourcepub fn into_filter<F>(self, f: F) -> Set<K>
pub fn into_filter<F>(self, f: F) -> Set<K>
Filter keys with a predicate, consuming the set.
Sourcepub fn filter_typed<F>(&self, pred: F) -> Set<K>
pub fn filter_typed<F>(&self, pred: F) -> Set<K>
Filter keys with a predicate over the typed, by-value decoded key.
Unlike Self::filter (which hands the closure a raw IndexKey), the
key is decoded to K first, so a product set yields native tuples and no
manual as_tuple().unwrap() unpacking is needed. The receiver’s K pins
the closure parameter, so it usually needs no annotation.
use oximo_core::Set;
let plants = Set::strings(["seattle", "san-diego"]);
// No-self-loop arcs; keys decoded to `(String, String)`.
let arcs = (&plants * &plants).filter_typed(|(p, q)| p != q);
assert_eq!(arcs.len(), 2);Sourcepub fn filter_ref<F>(&self, f: F) -> Set<K>
pub fn filter_ref<F>(&self, f: F) -> Set<K>
Borrowed-key filter that passes IndexKeyRef to the predicate.
Use when the set is borrowed and the filtered fraction is small or tuple
arity is large.
Sourcepub fn into_filter_ref<F>(self, f: F) -> Set<K>
pub fn into_filter_ref<F>(self, f: F) -> Set<K>
Consuming variant of Self::filter_ref.
Sourcepub fn into_filter_typed<F>(self, pred: F) -> Set<K>
pub fn into_filter_typed<F>(self, pred: F) -> Set<K>
Consuming typed filter that moves the set (see Self::into_filter).
pub fn len(&self) -> usize
pub fn is_empty(&self) -> bool
Sourcepub fn is_range(&self) -> bool
pub fn is_range(&self) -> bool
Whether the backing representation is the integer-range variant.
Sourcepub fn is_strings(&self) -> bool
pub fn is_strings(&self) -> bool
Whether the backing representation is the string-list variant.
Source§impl Set<usize>
impl Set<usize>
Sourcepub fn range<T>(r: Range<T>) -> Set<usize>where
T: PrimInt,
pub fn range<T>(r: Range<T>) -> Set<usize>where
T: PrimInt,
Build an integer index set from a range over any primitive integer
type. Accepts Range<i64>, Range<i32>, Range<usize>, etc.
The keys decode to usize.
Negative elements are accepted into the payload but panic when
decoded to usize.
§Panics
Panics if either range bound does not fit in i64.
Examples found in repository?
More examples
55fn main() -> Result<(), Box<dyn std::error::Error>> {
56 const T: usize = 12;
57
58 let demand: [f64; T] =
59 [120.0, 90.0, 80.0, 140.0, 160.0, 200.0, 220.0, 190.0, 150.0, 130.0, 100.0, 170.0];
60 let prod_cost: [f64; T] = [5.0, 5.0, 5.0, 5.5, 6.0, 6.5, 6.5, 6.0, 5.5, 5.0, 5.0, 5.5];
61 let setup_cost = 500.0;
62 let hold_cost = 2.0;
63 let capacity = 300.0;
64 let initial_inventory = 50.0;
65 let safety_stock = 30.0;
66
67 let m = Model::new("lot_sizing");
68 let periods = Set::range(0..T);
69
70 variable!(m, 0.0 <= x[t in periods] <= capacity);
71 variable!(m, h[t in periods] >= 0.0);
72 variable!(m, s[t in periods], Bin);
73
74 constraint!(m, inv_bal0, h[0] - x[0] == initial_inventory - demand[0]);
75 constraint!(m, inv_bal[t in 1..T], h[t] - h[t - 1] - x[t] == -demand[t]);
76 constraint!(m, setup[t in periods], x[t] <= capacity * s[t]);
77 constraint!(m, safety_stock, h[T - 1] >= safety_stock);
78
79 objective!(
80 m,
81 Min,
82 sum!(prod_cost[t] * x[t] + setup_cost * s[t] + hold_cost * h[t] for t in periods)
83 );
84
85 #[cfg(feature = "gurobi")]
86 let result = {
87 let opts = GurobiOptions::default()
88 .time_limit(std::time::Duration::from_secs(60))
89 .mip_gap(1e-4)
90 .verbose(true);
91 Gurobi.solve(&m, &opts)?
92 };
93
94 #[cfg(all(feature = "highs", not(feature = "gurobi")))]
95 let result = {
96 let opts = HighsOptions::default()
97 .time_limit(std::time::Duration::from_secs(60))
98 .mip_gap(1e-4)
99 .verbose(true);
100 Highs.solve(&m, &opts)?
101 };
102
103 println!("\nLot-Sizing Result");
104 println!("Status : {:?}", result.termination);
105 if let Some(obj) = result.objective() {
106 println!("Total cost: {obj:.2}");
107 }
108
109 println!(
110 "\n{:<8} {:>10} {:>10} {:>8} {:>12}",
111 "Period", "Produce", "Inventory", "Active", "Period cost"
112 );
113 println!("{}", "-".repeat(55));
114
115 let mut total_check = 0.0;
116 for t in 0..T {
117 let xt = result.value_of(x[t])?.unwrap_or(0.0);
118 let ht = result.value_of(h[t])?.unwrap_or(0.0);
119 let st = result.value_of(s[t])?.unwrap_or(0.0);
120 let period_cost = prod_cost[t] * xt + setup_cost * st + hold_cost * ht;
121 total_check += period_cost;
122 println!(
123 "{:<8} {:>10.1} {:>10.1} {:>8} {:>12.2}",
124 t + 1,
125 xt,
126 ht,
127 if (st - 1.0).abs() < 1e-6 { "Yes" } else { "No" },
128 period_cost
129 );
130 }
131 println!("{}", "-".repeat(55));
132 println!("{:<8} {:>10} {:>10} {:>8} {:>12.2}", "TOTAL", "", "", "", total_check);
133
134 Ok(())
135}50fn main() -> Result<(), Box<dyn std::error::Error>> {
51 use oximo::ClarabelOptions;
52 use oximo::prelude::*;
53 use oximo::solvers::Clarabel;
54
55 // Problem data
56 const N: usize = 4; // tanks
57 const TAU: usize = 1000; // time periods
58
59 // Forced-flow + diffusion matrix M + L.
60 const ML: [[f64; N]; N] = [
61 [-2.3, 0.3, 0.0, 0.0],
62 [1.3, -3.9, 0.3, 1.3],
63 [0.0, 2.3, -3.6, 0.3],
64 [0.0, 0.3, 1.3, -2.6],
65 ];
66
67 // C = diag(Qin), the external water inflow at each tank.
68 const QIN: [f64; N] = [2.0, 1.0, 1.0, 1.0];
69
70 // Total biomass mass allowed in the tanks per period: 1' Qin X(t) <= BUDGET.
71 const BUDGET: f64 = 3.0;
72
73 // Substrate inflow profiles Sin_i(t), indexed sin[i][p] with paper time t=p+1.
74 #[expect(clippy::needless_range_loop, clippy::cast_precision_loss)]
75 let sin: Vec<Vec<f64>> = {
76 let pi = std::f64::consts::PI;
77 let tau = TAU as f64;
78 let mut sin = vec![vec![0.0f64; TAU]; N];
79 for p in 0..TAU {
80 let t = (p + 1) as f64;
81 let ti = p + 1;
82 sin[0][p] = 1.0 + (4.0 * pi * t / tau).sin();
83 sin[1][p] = 0.0;
84 sin[2][p] = if ti > TAU / 4 && ti <= 3 * TAU / 4 { 0.5 } else { 0.0 };
85 sin[3][p] = 1.0 + (4.0 * pi * t / tau).cos();
86 }
87 sin
88 };
89
90 let m = Model::new("gradostat_multiperiod");
91 let tanks = Set::range(0..N);
92 let periods = Set::range(0..TAU);
93
94 variable!(m, s[i in tanks, p in periods] >= 0.0); // substrate S
95 variable!(m, x[i in tanks, p in periods] >= 0.0); // biomass X
96 variable!(m, kin[i in tanks, p in periods] >= 0.0); // kinetics T
97 variable!(m, xin[i in tanks, p in periods] >= 0.0); // biomass inflow Xin (decision)
98
99 // Dynamic substrate and biomass balances (25a, 25b), Euler-explicit, V=y=1.
100 constraint!(m, s_bal[i in tanks, p in periods],
101 s[i, (p + 1) % TAU] - s[i, p]
102 == -kin[i, p] + sum!(ML[i][j] * s[j, p] for j in tanks) + QIN[i] * sin[i][p]);
103 constraint!(m, x_bal[i in tanks, p in periods],
104 x[i, (p + 1) % TAU] - x[i, p]
105 == kin[i, p] + sum!(ML[i][j] * x[j, p] for j in tanks) + QIN[i] * xin[i, p]);
106
107 // Contois growth as a second-order cone (eq. 9, mu_max = K = 1).
108 soc_constraint!(m, growth[i in tanks, p in periods],
109 [s[i, p], kin[i, p], x[i, p]] <= x[i, p] + s[i, p] - kin[i, p]);
110 constraint!(m, st_diff[i in tanks, p in periods], s[i, p] - kin[i, p] >= 0.0);
111
112 // Per-period biomass-inflow budget, 1' Qin Xin(t) <= BUDGET.
113 constraint!(m, bio_budget[p in periods], sum!(QIN[i] * xin[i, p] for i in tanks) <= BUDGET);
114
115 // Maximize cumulative biogas, sum_{i,t} V_ii T_i with V = I (eq. 24, undiscounted).
116 objective!(m, Max, sum!(kin[i, p] for i in tanks, p in periods));
117
118 assert_eq!(m.kind(), ModelKind::SOCP);
119
120 let res = Clarabel.solve(&m, &ClarabelOptions::default())?;
121 assert_eq!(res.termination, TerminationStatus::Optimal);
122
123 // Exactness metric E = max_{i,t} |r(S,X)-T|/r(S,X), with the Contois
124 // kinetics r(S,X) = mu_max S X/(K X + S) evaluated at the solution.
125 let mut e_max = 0.0f64;
126 for i in 0..N {
127 for p in 0..TAU {
128 let sv = res.value_of(s[(i, p)])?.unwrap();
129 let xv = res.value_of(x[(i, p)])?.unwrap();
130 let tv = res.value_of(kin[(i, p)])?.unwrap();
131 let denom = sv + xv;
132 if denom > 1e-9 {
133 let r = sv * xv / denom;
134 if r > 1e-9 {
135 e_max = e_max.max((r - tv).abs() / r);
136 }
137 }
138 }
139 }
140
141 println!("Results:");
142 println!("Termination: {:?}", res.termination);
143 println!("Objective: {:.4}", res.objective().unwrap());
144 println!("Exactness E: {e_max:.3e}");
145 println!("Iterations: {}", res.iterations);
146 println!("Solve time: {:.3} s", res.solve_time.as_secs_f64());
147
148 Ok(())
149}Source§impl Set<String>
impl Set<String>
Sourcepub fn strings<I, S>(iter: I) -> Set<String>
pub fn strings<I, S>(iter: I) -> Set<String>
Examples found in repository?
49fn main() -> Result<(), Box<dyn std::error::Error>> {
50 // 34 chemicals: index = yXX - 1 (y01 -> 0, ..., y34 -> 33).
51 const CHEMICALS: [&str; 34] = [
52 "y01", "y02", "y03", "y04", "y05", "y06", "y07", "y08", "y09", "y10", "y11", "y12", "y13",
53 "y14", "y15", "y16", "y17", "y18", "y19", "y20", "y21", "y22", "y23", "y24", "y25", "y26",
54 "y27", "y28", "y29", "y30", "y31", "y32", "y33", "y34",
55 ];
56
57 // (reaction label, product index, reactant indices) - all 0-based.
58 // Transcribed from logicc set in GAMS REACTION model (SEQ=121).
59 let logicc: &[(&str, usize, &[usize])] = &[
60 ("rxn01", 3, &[0, 1, 2]), // y04 <- y01 + y02 + y03
61 ("rxn02", 5, &[3, 4]), // y06 <- y04 + y05
62 ("rxn03", 6, &[3, 4]), // y07 <- y04 + y05
63 ("rxn04", 2, &[3, 4]), // y03 <- y04 + y05
64 ("rxn05", 10, &[7, 8, 9]), // y11 <- y08 + y09 + y10
65 ("rxn06", 5, &[10, 11, 12]), // y06 <- y11 + y12 + y13
66 ("rxn07", 14, &[13, 8, 9, 4]), // y15 <- y14 + y09 + y10 + y05
67 ("rxn08", 5, &[14, 15, 16]), // y06 <- y15 + y16 + y17
68 ("rxn09", 5, &[17, 18, 11]), // y06 <- y18 + y19 + y12
69 ("rxn10", 19, &[17, 18, 11]), // y20 <- y18 + y19 + y12
70 ("rxn11", 8, &[20, 21]), // y09 <- y21 + y22
71 ("rxn12", 23, &[8, 22]), // y24 <- y09 + y23
72 ("rxn13", 17, &[23, 16]), // y18 <- y24 + y17
73 ("rxn14", 20, &[24, 25]), // y21 <- y25 + y26
74 ("rxn15", 26, &[24, 25]), // y27 <- y25 + y26
75 ("rxn16", 13, &[2, 27, 28]), // y14 <- y03 + y28 + y29
76 ("rxn17", 31, &[29, 30, 11]), // y32 <- y30 + y31 + y12
77 ("rxn18", 7, &[29, 30, 11]), // y08 <- y30 + y31 + y12
78 ("rxn19", 29, &[24, 32]), // y30 <- y25 + y33
79 ("rxn20", 12, &[24, 32]), // y13 <- y25 + y33
80 ("rxn21", 0, &[33, 2]), // y01 <- y34 + y03
81 ("rxn22", 33, &[13, 27]), // y34 <- y14 + y28
82 ];
83
84 // y02, y03, y05, y10, y12, y13, y17, y22, y25, y26, y28, y31, y33: fixed to 1.
85 let available: &[usize] = &[1, 2, 4, 9, 11, 12, 16, 21, 24, 25, 27, 30, 32];
86 // y16, y19: fixed to 0.
87 let unavailable: &[usize] = &[15, 18];
88
89 let m = Model::new("reaction_path");
90 let chemicals = Set::strings(CHEMICALS);
91
92 variable!(m, y[v in chemicals], Bin);
93 // Fix availability: raw materials/catalysts to 1, unavailable chemicals to 0.
94 for &i in available {
95 m.fix(y[CHEMICALS[i]], 1.0)?;
96 }
97 for &i in unavailable {
98 m.fix(y[CHEMICALS[i]], 0.0)?;
99 }
100
101 // sum_vv (1 - y[vv]) >= 1 - y[v]
102 // <=> y[v] - sum_vv y[vv] >= 1 - |reactants|
103 for &(_rx, prod, reactants) in logicc {
104 let n = reactants.len() as f64;
105 constraint!(m, y[CHEMICALS[prod]] - sum!(y[CHEMICALS[vv]] for vv in reactants) >= 1.0 - n);
106 }
107
108 objective!(m, Min, y["y06"]); // acetone
109
110 #[cfg(feature = "gams")]
111 let result = {
112 let opts = GamsOptions::default()
113 .time_limit(std::time::Duration::from_secs(60))
114 .solver(GamsSolverConfig::Cplex(GamsCplexOptions::default()))
115 .verbose(true);
116 let mut solver = Gams::new();
117 solver.solve(&m, &opts)?
118 };
119
120 #[cfg(all(feature = "highs", not(feature = "gams")))]
121 let result = Highs.solve(&m, &HighsOptions::default().verbose(true))?;
122
123 println!("Status : {:?}", result.termination);
124 if let Some(obj) = result.objective() {
125 println!(
126 "Acetone (y06, ch3coch3): {}",
127 if (obj - 1.0).abs() < 1e-6 { "Synthesizable" } else { "Not synthesizable" }
128 );
129 }
130
131 let synthesizable: Vec<&str> = CHEMICALS
132 .iter()
133 .copied()
134 .filter(|name| (result.value_of(y[*name]).unwrap().unwrap_or(0.0) - 1.0).abs() < 1e-6)
135 .collect();
136 if !synthesizable.is_empty() {
137 println!("Synthesizable chemicals: {}", synthesizable.join(", "));
138 }
139
140 Ok(())
141}Trait Implementations§
Source§impl<'a, K> IntoIterator for &'a Set<K>
impl<'a, K> IntoIterator for &'a Set<K>
Auto Trait Implementations§
impl<K> Freeze for Set<K>
impl<K> RefUnwindSafe for Set<K>
impl<K> Send for Set<K>
impl<K> Sync for Set<K>
impl<K> Unpin for Set<K>
impl<K> UnsafeUnpin for Set<K>
impl<K> UnwindSafe for Set<K>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> DistributionExt for Twhere
T: ?Sized,
impl<T> DistributionExt for Twhere
T: ?Sized,
impl<T, U> Imply<T> for U
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more