1use std::convert::TryInto;
7use std::ffi::CString;
8use std::iter::IntoIterator;
9#[allow(unused_imports)] use std::ptr::{null, null_mut};
11
12use cstr_enum::AsCStr;
13
14use crate::ffi;
15use crate::ffi::{c_char, c_int};
16use crate::model_object::*;
17use crate::util::{copy_c_str, AsPtr};
18use crate::{ConstrSense, Model, ModelSense, Result, Status, VarType};
19
20#[allow(missing_docs)]
21mod attr_enums {
22    include!(concat!(env!("OUT_DIR"), "/attr_enums.rs"));
23    }
25#[doc(inline)]
26pub use attr_enums::enum_exports::*;
27#[doc(inline)]
28pub use attr_enums::variant_exports as attr;
29
30mod private {
31    use super::*;
32
33    pub trait IntAttr {}
34    pub trait CharAttr {}
35    pub trait StrAttr {}
36    pub trait DoubleAttr {}
37
38    pub trait ObjAttr {
39        type Obj: ModelObject;
40    }
41}
42
43use private::*;
44
45pub trait StringLike: Into<Vec<u8>> {}
47
48impl StringLike for String {}
49impl<'a> StringLike for &'a str {}
50
51pub trait ObjAttrGet<O, V> {
53    fn get(&self, model: &Model, idx: i32) -> Result<V>;
55    fn get_batch<I: IntoIterator<Item = Result<i32>>>(
57        &self,
58        model: &Model,
59        idx: I,
60    ) -> Result<Vec<V>>;
61}
62
63pub trait ObjAttrSet<O, V> {
65    fn set(&self, model: &Model, idx: i32, val: V) -> Result<()>;
67    fn set_batch<I: IntoIterator<Item = (Result<i32>, V)>>(
69        &self,
70        model: &Model,
71        idx_val_pairs: I,
72    ) -> Result<()>;
73}
74
75macro_rules! impl_obj_get {
76    ($t:ty, $default:expr, $get:path, $getbatch:path) => {
77        fn get(&self, model: &Model, idx: i32) -> Result<$t> {
78            let mut val = $default;
79            unsafe {
80                let m = model.as_mut_ptr();
81                let code = $get(m, self.as_cstr().as_ptr(), idx, &mut val);
82                model.check_apicall(code)?;
83            }
84            Ok(val)
85        }
86
87        fn get_batch<I: IntoIterator<Item = Result<i32>>>(
88            &self,
89            model: &Model,
90            inds: I,
91        ) -> Result<Vec<$t>> {
92            let inds: Result<Vec<_>> = inds.into_iter().collect();
93            let inds = inds?;
94            let mut vals = vec![$default; inds.len()];
95
96            unsafe {
97                model.check_apicall($getbatch(
98                    model.as_mut_ptr(),
99                    self.as_cstr().as_ptr(),
100                    inds.len() as c_int,
101                    inds.as_ptr(),
102                    vals.as_mut_ptr(),
103                ))?;
104            }
105
106            Ok(vals)
107        }
108    };
109}
110
111macro_rules! impl_obj_set {
112    ($t:ty, $default:expr, $set:path, $setbatch:path) => {
113        fn set(&self, model: &Model, idx: i32, val: $t) -> Result<()> {
114            unsafe {
115                let m = model.as_mut_ptr();
116                let code = $set(m, self.as_cstr().as_ptr(), idx, val);
117                model.check_apicall(code)
118            }
119        }
120
121        fn set_batch<I: IntoIterator<Item = (Result<i32>, $t)>>(
122            &self,
123            model: &Model,
124            idx_val_pairs: I,
125        ) -> Result<()> {
126            let idx_val_pairs = idx_val_pairs.into_iter();
127            let size_hint = idx_val_pairs.size_hint().0;
128            let mut inds = Vec::with_capacity(size_hint);
129            let mut vals = Vec::with_capacity(size_hint);
130
131            for (i, v) in idx_val_pairs {
132                inds.push(i?);
133                vals.push(v);
134            }
135
136            unsafe {
137                model.check_apicall($setbatch(
138                    model.as_mut_ptr(),
139                    self.as_cstr().as_ptr(),
140                    inds.len() as c_int,
141                    inds.as_ptr(),
142                    vals.as_ptr(),
143                ))?;
144            }
145
146            Ok(())
147        }
148    };
149}
150
151macro_rules! impl_obj_get_custom {
154    ($t:path, $default:expr, $get:path, $getbatch:path) => {
155        fn get(&self, model: &Model, idx: i32) -> Result<$t> {
156            let mut val = $default;
157            unsafe {
158                let m = model.as_mut_ptr();
159                let code = $get(m, self.as_cstr().as_ptr(), idx, &mut val);
160                model.check_apicall(code)?;
161            }
162            Ok(val.try_into().unwrap())
163        }
164
165        fn get_batch<I: IntoIterator<Item = Result<i32>>>(
166            &self,
167            model: &Model,
168            inds: I,
169        ) -> Result<Vec<$t>> {
170            let inds: Result<Vec<_>> = inds.into_iter().collect();
171            let inds = inds?;
172            let mut vals = vec![$default; inds.len()];
173
174            unsafe {
175                model.check_apicall($getbatch(
176                    model.as_mut_ptr(),
177                    self.as_cstr().as_ptr(),
178                    inds.len() as c_int,
179                    inds.as_ptr(),
180                    vals.as_mut_ptr(),
181                ))?;
182            }
183
184            let vals = vals
185                .into_iter()
186                .map(|ch| (ch as c_char).try_into().unwrap())
187                .collect();
188            Ok(vals)
189        }
190    };
191}
192
193impl<A> ObjAttrGet<A::Obj, i32> for A
194where
195    A: IntAttr + ObjAttr + AsCStr,
196{
197    impl_obj_get! { i32, i32::MIN, ffi::GRBgetintattrelement, ffi::GRBgetintattrlist }
198}
199
200impl<A> ObjAttrSet<A::Obj, i32> for A
201where
202    A: IntAttr + ObjAttr + AsCStr,
203{
204    impl_obj_set! { i32, i32::MIN, ffi::GRBsetintattrelement, ffi::GRBsetintattrlist }
205}
206
207impl<A> ObjAttrGet<A::Obj, f64> for A
208where
209    A: DoubleAttr + ObjAttr + AsCStr,
210{
211    impl_obj_get! { f64, f64::MIN, ffi::GRBgetdblattrelement, ffi::GRBgetdblattrlist }
212}
213
214impl<A> ObjAttrSet<A::Obj, f64> for A
215where
216    A: DoubleAttr + ObjAttr + AsCStr,
217{
218    impl_obj_set! { f64, f64::MIN, ffi::GRBsetdblattrelement, ffi::GRBsetdblattrlist }
219}
220
221impl<A> ObjAttrGet<A::Obj, c_char> for A
222where
223    A: CharAttr + ObjAttr + AsCStr,
224{
225    impl_obj_get! { c_char, 0i8, ffi::GRBgetcharattrelement, ffi::GRBgetcharattrlist }
226}
227
228impl<A> ObjAttrSet<A::Obj, c_char> for A
229where
230    A: CharAttr + ObjAttr + AsCStr,
231{
232    impl_obj_set! { c_char, 0i8, ffi::GRBsetcharattrelement, ffi::GRBsetcharattrlist }
233}
234
235impl ObjAttrSet<Var, c_char> for VarVTypeAttr {
236    impl_obj_set! { c_char, 0i8, ffi::GRBsetcharattrelement, ffi::GRBsetcharattrlist }
237}
238
239impl ObjAttrSet<Var, VarType> for VarVTypeAttr {
240    fn set(&self, model: &Model, idx: i32, val: VarType) -> Result<()> {
241        self.set(model, idx, val as c_char)
242    }
243    fn set_batch<I: IntoIterator<Item = (Result<i32>, VarType)>>(
244        &self,
245        model: &Model,
246        idx_val_pairs: I,
247    ) -> Result<()> {
248        self.set_batch(
249            model,
250            idx_val_pairs
251                .into_iter()
252                .map(|(idx, vt)| (idx, vt as c_char)),
253        )
254    }
255}
256
257impl ObjAttrGet<Var, VarType> for VarVTypeAttr {
258    impl_obj_get_custom! { VarType, 0i8, ffi::GRBgetcharattrelement, ffi::GRBgetcharattrlist}
259}
260
261impl ObjAttrSet<Constr, c_char> for ConstrSenseAttr {
262    impl_obj_set! { c_char, 0i8, ffi::GRBsetcharattrelement, ffi::GRBsetcharattrlist }
263}
264
265impl ObjAttrSet<Constr, ConstrSense> for ConstrSenseAttr {
266    fn set(&self, model: &Model, idx: i32, val: ConstrSense) -> Result<()> {
267        self.set(model, idx, val as c_char)
268    }
269    fn set_batch<I: IntoIterator<Item = (Result<i32>, ConstrSense)>>(
270        &self,
271        model: &Model,
272        idx_val_pairs: I,
273    ) -> Result<()> {
274        self.set_batch(
275            model,
276            idx_val_pairs
277                .into_iter()
278                .map(|(idx, vt)| (idx, vt as c_char)),
279        )
280    }
281}
282
283impl ObjAttrGet<Constr, ConstrSense> for ConstrSenseAttr {
284    impl_obj_get_custom! { ConstrSense, 0i8, ffi::GRBgetcharattrelement, ffi::GRBgetcharattrlist}
285}
286
287impl<A> ObjAttrGet<A::Obj, String> for A
294where
295    A: StrAttr + AsCStr + ObjAttr,
296{
297    fn get(&self, model: &Model, idx: i32) -> Result<String> {
298        unsafe {
299            let mut s: ffi::c_str = std::ptr::null();
300            model.check_apicall(ffi::GRBgetstrattrelement(
301                model.as_mut_ptr(),
302                self.as_cstr().as_ptr(),
303                idx,
304                &mut s,
305            ))?;
306            if s.is_null() {
307                return Ok(String::new());
308            }
309            Ok(copy_c_str(s))
310        }
311    }
312
313    fn get_batch<I: IntoIterator<Item = Result<i32>>>(
314        &self,
315        model: &Model,
316        idx: I,
317    ) -> Result<Vec<String>> {
318        let inds: Result<Vec<_>> = idx.into_iter().collect();
319        let inds = inds?;
320
321        unsafe {
322            let mut cstrings: Vec<*const c_char> = vec![std::ptr::null(); inds.len()];
323            model.check_apicall(ffi::GRBgetstrattrlist(
324                model.as_mut_ptr(),
325                self.as_cstr().as_ptr(),
326                inds.len() as c_int,
327                inds.as_ptr(),
328                cstrings.as_mut_ptr(),
329            ))?;
330
331            let strings = cstrings.into_iter().map(|s| copy_c_str(s)).collect();
332            Ok(strings)
333        }
334    }
335}
336
337impl<A, T> ObjAttrSet<A::Obj, T> for A
338where
339    A: StrAttr + AsCStr + ObjAttr,
340    T: StringLike,
341{
342    fn set(&self, model: &Model, idx: i32, val: T) -> Result<()> {
343        let val = CString::new(val)?;
344        unsafe {
345            model.check_apicall(ffi::GRBsetstrattrelement(
346                model.as_mut_ptr(),
347                self.as_cstr().as_ptr(),
348                idx,
349                val.as_ptr(),
350            ))?;
351        }
352        Ok(())
353    }
354
355    fn set_batch<I: IntoIterator<Item = (Result<i32>, T)>>(
356        &self,
357        model: &Model,
358        idx_val_pairs: I,
359    ) -> Result<()> {
360        let idx_val_pairs = idx_val_pairs.into_iter();
361        let size_hint = idx_val_pairs.size_hint().0;
362        let mut inds = Vec::with_capacity(size_hint);
363        let mut cstrings = Vec::with_capacity(size_hint);
364        let mut cstr_ptrs = Vec::with_capacity(size_hint);
365
366        for (i, s) in idx_val_pairs {
367            let cs = CString::new(s)?;
368            inds.push(i?);
369            cstr_ptrs.push(cs.as_ptr());
370            cstrings.push(cs);
371        }
372
373        unsafe {
374            model.check_apicall(ffi::GRBsetstrattrlist(
375                model.as_mut_ptr(),
376                self.as_cstr().as_ptr(),
377                inds.len() as c_int,
378                inds.as_ptr(),
379                cstr_ptrs.as_ptr(),
380            ))
381        }
382    }
383}
384
385pub trait ModelAttrGet<V> {
387    fn get(&self, model: &Model) -> Result<V>;
389}
390
391pub trait ModelAttrSet<V> {
393    fn set(&self, model: &Model, val: V) -> Result<()>;
395}
396
397macro_rules! impl_model_attr {
398    ($target:path, $t:ty, $default:expr, $get:path, $set:path) => {
399        impl ModelAttrGet<$t> for $target {
400            fn get(&self, model: &Model) -> Result<$t> {
401                let mut val = $default;
402                unsafe {
403                    model.check_apicall($get(
404                        model.as_mut_ptr(),
405                        self.as_cstr().as_ptr(),
406                        &mut val,
407                    ))?
408                }
409                Ok(val)
410            }
411        }
412
413        impl ModelAttrSet<$t> for $target {
414            fn set(&self, model: &Model, val: $t) -> Result<()> {
415                unsafe {
416                    model.check_apicall($set(model.as_mut_ptr(), self.as_cstr().as_ptr(), val))
417                }
418            }
419        }
420    };
421}
422
423impl_model_attr! { ModelIntAttr, i32, i32::MIN, ffi::GRBgetintattr, ffi::GRBsetintattr }
424impl_model_attr! { ModelDoubleAttr, f64, f64::NAN, ffi::GRBgetdblattr, ffi::GRBsetdblattr }
425
426impl ModelAttrGet<String> for ModelStrAttr {
427    fn get(&self, model: &Model) -> Result<String> {
428        unsafe {
429            let mut val: *const c_char = null_mut();
430            model.check_apicall(ffi::GRBgetstrattr(
431                model.as_mut_ptr(),
432                self.as_cstr().as_ptr(),
433                &mut val,
434            ))?;
435            if val.is_null() {
436                return Ok(String::new());
437            }
438            Ok(copy_c_str(val))
439        }
440    }
441}
442
443impl<T: Into<Vec<u8>>> ModelAttrSet<T> for ModelStrAttr {
444    fn set(&self, model: &Model, val: T) -> Result<()> {
445        let val = CString::new(val)?;
446        unsafe {
447            model.check_apicall(ffi::GRBsetstrattr(
448                model.as_mut_ptr(),
449                self.as_cstr().as_ptr(),
450                val.as_ptr(),
451            ))
452        }
453    }
454}
455
456impl ModelAttrGet<ModelSense> for ModelModelSenseAttr {
457    fn get(&self, model: &Model) -> Result<ModelSense> {
458        let mut val = i32::MIN;
459        unsafe {
460            model.check_apicall(ffi::GRBgetintattr(
461                model.as_mut_ptr(),
462                self.as_cstr().as_ptr(),
463                &mut val,
464            ))?
465        }
466        Ok(val.try_into().unwrap())
467    }
468}
469
470impl ModelAttrSet<i32> for ModelModelSenseAttr {
471    fn set(&self, model: &Model, val: i32) -> Result<()> {
472        unsafe {
473            model.check_apicall(ffi::GRBsetintattr(
474                model.as_mut_ptr(),
475                self.as_cstr().as_ptr(),
476                val,
477            ))
478        }
479    }
480}
481
482impl ModelAttrSet<ModelSense> for ModelModelSenseAttr {
483    fn set(&self, model: &Model, val: ModelSense) -> Result<()> {
484        self.set(model, val as i32)
485    }
486}
487
488impl ModelAttrGet<Status> for ModelStatusAttr {
489    fn get(&self, model: &Model) -> Result<Status> {
490        let mut val = i32::MIN;
491        unsafe {
492            model.check_apicall(ffi::GRBgetintattr(
493                model.as_mut_ptr(),
494                self.as_cstr().as_ptr(),
495                &mut val,
496            ))?
497        }
498        Ok(val.try_into().unwrap())
499    }
500}
501
502#[cfg(test)]
503mod tests {
504    use super::*;
505    use crate as grb;
506    use crate::SOSType;
507    use std::ffi::CStr;
508    use std::marker::PhantomData;
509
510    #[derive(Debug, Clone)]
511    struct Attribute<T>(CString, PhantomData<T>);
512
513    impl<T> Attribute<T> {
514        pub fn new(s: String) -> Self {
515            Attribute(CString::new(s).unwrap(), PhantomData)
516        }
517    }
518
519    impl<T: ModelObject> Attribute<T> {
520        pub fn get<V>(self, model: &Model, obj: &T) -> Option<crate::Error>
521        where
522            Self: ObjAttrGet<T, V>,
523        {
524            model.get_obj_attr::<_, _, V>(self, obj).err()
525        }
526    }
527
528    impl Attribute<Model> {
529        pub fn get_model<V>(self, model: &Model) -> Option<crate::Error>
530        where
531            Self: ModelAttrGet<V>,
532        {
533            model.get_attr::<_, V>(self).err()
534        }
535    }
536
537    impl<T> AsCStr for Attribute<T> {
538        fn as_cstr(&self) -> &CStr {
539            &self.0
540        }
541    }
542
543    impl<T> IntAttr for Attribute<T> {}
544    impl<T> DoubleAttr for Attribute<T> {}
545    impl<T> StrAttr for Attribute<T> {}
546    impl<T> CharAttr for Attribute<T> {}
547
548    impl ObjAttr for Attribute<Var> {
549        type Obj = Var;
550    }
551
552    impl ObjAttr for Attribute<Constr> {
553        type Obj = Constr;
554    }
555    impl ObjAttr for Attribute<GenConstr> {
556        type Obj = GenConstr;
557    }
558    impl ObjAttr for Attribute<QConstr> {
559        type Obj = QConstr;
560    }
561    impl ObjAttr for Attribute<SOS> {
562        type Obj = SOS;
563    }
564
565    impl_model_attr! { Attribute<Model>, i32, i32::MIN, ffi::GRBgetintattr, ffi::GRBsetintattr }
566    impl_model_attr! { Attribute<Model>, f64, f64::NAN, ffi::GRBgetdblattr, ffi::GRBsetdblattr }
567
568    impl ModelAttrGet<String> for Attribute<Model> {
569        fn get(&self, model: &Model) -> Result<String> {
570            unsafe {
571                let mut val: *const c_char = null_mut();
572                model.check_apicall(ffi::GRBgetstrattr(
573                    model.as_mut_ptr(),
574                    self.as_cstr().as_ptr(),
575                    &mut val,
576                ))?;
577                if val.is_null() {
578                    return Ok(String::new());
579                }
580                Ok(copy_c_str(val))
581            }
582        }
583    }
584
585    #[test]
586    fn attribute_names() -> anyhow::Result<()> {
587        let params: Vec<_> =
588            std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/build/attrs.csv"))
589                .unwrap()
590                .lines()
591                .skip(1)
592                .map(|line| {
593                    let mut line = line.split(",");
594                    let param = line.next().unwrap();
595                    let ty = line.next().unwrap();
596                    let obj = line.next().unwrap();
597                    assert_eq!(line.next(), None);
598                    (param.to_string(), ty.to_string(), obj.to_string())
599                })
600                .collect();
601
602        let mut model = crate::Model::new("test")?;
603        let var = crate::add_ctsvar!(model)?;
604        let x = crate::add_binvar!(model)?;
605        let y = crate::add_binvar!(model)?;
606        let constraint = model.add_constr("", crate::c!(var >= 1))?;
607        let gconstraint = model.add_genconstr_indicator("", x, true, crate::c!(var >= y))?;
608        let qconstraint = model.add_qconstr("", crate::c!(var * var >= 1))?;
609
610        let sos = model.add_sos(vec![(x, 1.0), (y, 1.0)], SOSType::Ty1)?;
611        model.optimize()?;
612
613        let mut err_count = 0;
614        for (a, ty, obj) in params {
615            let ty = ty.as_str();
616            let obj = obj.as_str();
617            let a_name = a.clone();
618            let err = match (ty, obj) {
620                ("dbl", "var") => Attribute::new(a).get::<f64>(&model, &var),
621                ("int", "var") => Attribute::new(a).get::<i32>(&model, &var),
622                ("str", "var") => Attribute::new(a).get::<String>(&model, &var),
623
624                ("dbl", "constr") => Attribute::new(a).get::<f64>(&model, &constraint),
625                ("int", "constr") => Attribute::new(a).get::<i32>(&model, &constraint),
626                ("str", "constr") => Attribute::new(a).get::<String>(&model, &constraint),
627
628                ("dbl", "gconstr") => Attribute::new(a).get::<f64>(&model, &gconstraint),
629                ("int", "gconstr") => Attribute::new(a).get::<i32>(&model, &gconstraint),
630                ("str", "gconstr") => Attribute::new(a).get::<String>(&model, &gconstraint),
631
632                ("dbl", "qconstr") => Attribute::new(a).get::<f64>(&model, &qconstraint),
633                ("int", "qconstr") => Attribute::new(a).get::<i32>(&model, &qconstraint),
634                ("str", "qconstr") => Attribute::new(a).get::<String>(&model, &qconstraint),
635                ("chr", "qconstr") => Attribute::new(a).get::<c_char>(&model, &qconstraint),
636
637                ("dbl", "sos") => Attribute::new(a).get::<f64>(&model, &sos),
638                ("int", "sos") => Attribute::new(a).get::<i32>(&model, &sos),
639                ("str", "sos") => Attribute::new(a).get::<String>(&model, &sos),
640
641                ("dbl", "model") => Attribute::new(a).get_model::<f64>(&model),
642                ("int", "model") => Attribute::new(a).get_model::<i32>(&model),
643                ("str", "model") => Attribute::new(a).get_model::<String>(&model),
644
645                ("custom", _) => None,
646
647                _ => panic!("missing test for: {} {}", ty, obj),
648            };
649
650            if let Some(err) = err {
651                match err {
652                    crate::Error::FromAPI(_, 10005) => {}
654                    crate::Error::FromAPI(_, 10008) => {}
656                    err => {
657                        eprintln!("failed to get {a_name}: {err}");
658                        err_count += 1;
659                    }
660                }
661            }
662        }
663        if err_count > 0 {
664            anyhow::bail!("{err_count} failures")
665        }
666
667        Ok(())
668    }
669}