grb 1.3.0

A Rust API for Gurobi optimizer
Documentation
use std::borrow::Borrow;
use std::ffi::CString;
use std::mem::transmute;
use std::ptr::{null, null_mut};
use std::sync::atomic::{AtomicU32, Ordering};

use grb_sys2::{c_int, GRBmodel};
use grb_sys2 as ffi;

use crate::attribute::{ModelAttrGet, ModelAttrSet, ObjAttrGet, ObjAttrSet};
use crate::callback::{callback_wrapper, UserCallbackData};
use crate::constr::{IneqExpr, RangeExpr};
use crate::expr::{LinExpr, QuadExpr};
use crate::model_object::IdxManager;
use crate::parameter::{ParamGet, ParamSet};
use crate::prelude::*;
use crate::util::AsPtr;
use crate::{Error, Result};

/// Gurobi Model object.
///
/// This will be where the bulk of interactions with Gurobi occur.
pub struct Model {
    ptr: *mut GRBmodel,
    #[allow(dead_code)]
    id: u32,
    env: Env,
    pub(crate) vars: IdxManager<Var>,
    pub(crate) constrs: IdxManager<Constr>,
    pub(crate) qconstrs: IdxManager<QConstr>,
    pub(crate) sos: IdxManager<SOS>,
}

macro_rules! impl_object_list_getter {
    ($name:ident, $t:ty, $attr:ident, $noun:literal) => {
      #[doc="Retrieve the "]
      #[doc=$noun]
      #[doc=" in the model. \n\n # Errors\nReturns an error if a model update is needed"]
      pub fn $name<'a>(&'a self) -> Result<&'a [$t]> {
        if self.$attr.model_update_needed() {  Err(Error::ModelUpdateNeeded)  }
        else  { Ok(self.$attr.objects()) }
      }
    };
}

impl AsPtr for Model {
    type Ptr = GRBmodel;
    unsafe fn as_mut_ptr(&self) -> *mut GRBmodel {
        self.ptr
    }
}

impl Model {
    fn next_id() -> u32 {
        static NEXT_ID: AtomicU32 = AtomicU32::new(0);
        NEXT_ID.fetch_add(1, Ordering::Relaxed)
    }

    fn model_update_needed(&self) -> bool {
        self.vars.model_update_needed()
            || self.constrs.model_update_needed()
            || self.qconstrs.model_update_needed()
            || self.sos.model_update_needed()
    }

    fn build_idx_arrays_obj_obj<O1, O2, T>(
        &self,
        iter: impl Iterator<Item = (O1, O2, T)>,
    ) -> Result<(Vec<i32>, Vec<i32>, Vec<T>)>
    where
        O1: ModelObject,
        O2: ModelObject,
    {
        let n = iter.size_hint().0;
        let mut ov1 = Vec::with_capacity(n);
        let mut ov2 = Vec::with_capacity(n);
        let mut tv = Vec::with_capacity(n);

        for (o1, o2, t) in iter {
            ov1.push(self.get_index_build(&o1)?);
            ov2.push(self.get_index_build(&o2)?);
            tv.push(t);
        }

        Ok((ov1, ov2, tv))
    }

    fn build_idx_arrays_obj<O, T>(
        &self,
        iter: impl Iterator<Item = (O, T)>,
    ) -> Result<(Vec<i32>, Vec<T>)>
    where
        O: ModelObject,
    {
        let n = iter.size_hint().0;
        let mut ov = Vec::with_capacity(n);
        let mut tv = Vec::with_capacity(n);

        for (o, t) in iter {
            ov.push(self.get_index_build(&o)?);
            tv.push(t);
        }

        Ok((ov, tv))
    }

    #[inline]
    pub(crate) fn get_index<O: ModelObject>(&self, item: &O) -> Result<i32> {
        O::idx_manager(&self).get_index(item)
    }

    #[inline]
    pub(crate) fn get_index_build<O: ModelObject>(&self, item: &O) -> Result<i32> {
        O::idx_manager(&self).get_index_build(item)
    }

    #[inline]
    pub(crate) fn get_coeffs_indices_build(&self, expr: &LinExpr) -> Result<(Vec<i32>, Vec<f64>)> {
        let nterms = expr.num_terms();
        let mut inds = Vec::with_capacity(nterms);
        let mut coeff = Vec::with_capacity(nterms);
        for (x, &c) in expr.iter_terms() {
            inds.push(self.get_index_build(x)?);
            coeff.push(c);
        }
        Ok((inds, coeff))
    }

    #[inline]
    pub(crate) fn get_qcoeffs_indices_build(
        &self,
        expr: &QuadExpr,
    ) -> Result<(Vec<i32>, Vec<i32>, Vec<f64>)> {
        let nqterms = expr.num_qterms();
        let mut rowinds = Vec::with_capacity(nqterms);
        let mut colinds = Vec::with_capacity(nqterms);
        let mut coeff = Vec::with_capacity(nqterms);
        for ((x, y), &c) in expr.iter_qterms() {
            rowinds.push(self.get_index_build(x)?);
            colinds.push(self.get_index_build(y)?);
            coeff.push(c);
        }
        Ok((rowinds, colinds, coeff))
    }

    /// Create the `Model` object from a raw pointer returned by a Gurobi routine.
    ///
    /// # Safety
    /// Here we assume that the `GRBEnv` is tied to a specific `GRBModel`
    /// In other words, the pointer returned by GRBgetenv(model) is unique to
    /// that model.  It is explicitly stated in the docs for
    /// [`GRBnewmodel`](https://www.gurobi.com/documentation/9.1/refman/c_newmodel.html)
    /// that the environment the user supplies is copied,  but must be assumed for other
    /// Gurobi routines that create new `GRBmodel`s like
    /// [`GRBfeasrelax`](https://www.gurobi.com/documentation/9.1/refman/c_feasrelax.html),
    /// [`GRBfixmodel`](https://www.gurobi.com/documentation/9.1/refman/c_fixmodel.html)
    /// and [`GRBreadmodel`](https://www.gurobi.com/documentation/9.1/refman/c_readmodel.html)
    /// This assumption is necessary to prevent a double free when a `Model` object is dropped,
    /// which frees the `GRBModel` and triggers the drop of a `Env`, which in turn
    /// frees the `GRBEnv`.  The `*copies_env` tests in this module validate this assumption.
    fn from_raw(env: &Env, model: *mut GRBmodel) -> Result<Model> {
        let env_ptr = unsafe { ffi::GRBgetenv(model) };
        if env_ptr.is_null() {
            return Err(Error::FromAPI(
                "Failed to retrieve GRBenv from given model".to_owned(),
                2002,
            ));
        }
        let env = unsafe { Env::new_gurobi_allocated(env, env_ptr) };
        let id = Model::next_id();

        let mut model = Model {
            ptr: model,
            id,
            env,
            vars: IdxManager::new(id),
            constrs: IdxManager::new(id),
            qconstrs: IdxManager::new(id),
            sos: IdxManager::new(id),
        };

        let nvars = model.get_attr(attr::NumVars)?;
        let nconstr = model.get_attr(attr::NumConstrs)?;
        let nqconstr = model.get_attr(attr::NumQConstrs)?;
        let sos = model.get_attr(attr::NumSOS)?;

        model.vars = IdxManager::new_with_existing_obj(id, nvars as usize);
        model.constrs = IdxManager::new_with_existing_obj(id, nconstr as usize);
        model.qconstrs = IdxManager::new_with_existing_obj(id, nqconstr as usize);
        model.sos = IdxManager::new_with_existing_obj(id, sos as usize);

        Ok(model)
    }

    /// Create a new model with the given environment.  The original environment is
    /// copied by Gurobi.  To modify the environment of the model, use [`Model::get_env_mut`].
    ///
    /// # Examples
    /// ```
    /// # use grb::prelude::*;
    /// let mut env = Env::new("")?;
    /// env.set(param::OutputFlag, 0)?;
    ///
    /// let mut model = Model::with_env("Model", &env)?;
    /// assert_eq!(model.get_param(param::OutputFlag)?,  0);
    ///
    /// // Equivalent to model.set_param(param::OutputFlag, 1)?
    /// model.get_env_mut().set(param::OutputFlag, 1)?;
    ///
    /// assert_eq!(env.get(param::OutputFlag).unwrap(), 0); // original env is unchanged
    /// # Ok::<(), grb::Error>(())
    /// ```
    pub fn with_env(modelname: &str, env: impl Borrow<Env>) -> Result<Model> {
        let env = env.borrow();
        let modelname = CString::new(modelname)?;
        let mut model = null_mut();
        env.check_apicall(unsafe {
            ffi::GRBnewmodel(
                env.as_mut_ptr(),
                &mut model,
                modelname.as_ptr(),
                0,
                null(),
                null(),
                null(),
                null(),
                null(),
            )
        })?;
        Self::from_raw(env, model)
    }

    /// Create a new model with the default environment, which is lazily initialised.
    pub fn new(modelname: &str) -> Result<Model> {
        Env::GLOBAL_DEFAULT.with(|env| Model::with_env(modelname, env))
    }

    /// Create a copy of the model.  This method is fallible due to the lazy update approach and the underlying
    /// Gurobi C API, so a [`Clone`] implementation is not provided.
    ///
    /// # Errors
    ///  * [`Error::FromAPI`] if a Gurobi error occurs
    ///  * [`Error::ModelUpdateNeeded`] if model objects have been added to the model since the last update.
    pub fn try_clone(&self) -> Result<Model> {
        if self.model_update_needed() {
            return Err(Error::ModelUpdateNeeded);
        }

        let copied = unsafe { ffi::GRBcopymodel(self.ptr) };
        if copied.is_null() {
            return Err(Error::FromAPI(
                "Failed to create a copy of the model".to_owned(),
                20002,
            ));
        }

        Model::from_raw(&self.env, copied)
    }

    /// Read a model from a file.  See the [manual](https://www.gurobi.com/documentation/9.1/refman/c_readmodel.html) for accepted file formats.
    pub fn read_from(filename: &str, env: &Env) -> Result<Model> {
        let filename = CString::new(filename)?;
        let mut model = null_mut();
        env.check_apicall(unsafe {
            ffi::GRBreadmodel(env.as_mut_ptr(), filename.as_ptr(), &mut model)
        })?;
        Self::from_raw(env, model)
    }

    /// Create the fixed model associated with the current MIP model.
    ///
    /// The model must be MIP and have a solution loaded. In the fixed model,
    /// each integer variable is fixed to the value that it takes in the current MIP solution.
    pub fn fixed(&mut self) -> Result<Model> {
        let mut fixed: *mut GRBmodel = null_mut();
        self.check_apicall(unsafe { ffi::GRBfixmodel(self.ptr, &mut fixed) })?;
        debug_assert!(!fixed.is_null());
        Model::from_raw(&self.env, fixed)
    }

    /// Get shared reference to the environment associated with the model.
    pub fn get_env(&self) -> &Env {
        &self.env
    }

    /// Get mutable reference to the environment associated with the model.
    pub fn get_env_mut(&mut self) -> &mut Env {
        &mut self.env
    }

    /// Apply all queued modification of the model and update internal lookups.
    ///
    /// Some operations like [`Model::try_clone`] require this method to be called.
    ///
    /// # Examples
    /// ```
    /// # use grb::prelude::*;
    /// let mut m = Model::new("model")?;
    /// let x = add_ctsvar!(m);
    ///
    /// assert_eq!(m.try_clone().err().unwrap(), grb::Error::ModelUpdateNeeded);
    ///
    /// m.update();
    /// assert!(m.try_clone().is_ok());
    ///
    /// # Ok::<(), grb::Error>(())
    /// ```
    pub fn update(&mut self) -> Result<()> {
        self.vars.update();
        self.constrs.update();
        self.qconstrs.update();
        self.sos.update();
        self.check_apicall(unsafe { ffi::GRBupdatemodel(self.ptr) })?;
        Ok(())
    }

    /// Query update mode. See [https://www.gurobi.com/documentation/9.1/refman/updatemode.html]
    fn update_mode_lazy(&self) -> Result<bool> {
        //  0 => pending until update() or optimize() called.
        //  1 => all changes are immediate
        Ok(self.env.get(param::UpdateMode)? == 0)
    }

    /// Optimize the model synchronously.  This method will always trigger a [`Model::update`].
    pub fn optimize(&mut self) -> Result<()> {
        self.update()?;
        self.check_apicall(unsafe { ffi::GRBoptimize(self.ptr) })
    }

    /// Optimize the model with a callback.  The callback is any type that implements the
    /// [`Callback`] trait.  Closures, and anything else that implements `FnMut(CbCtx) -> Result<()>`
    /// implement the `Callback` trait automatically.   This method will always trigger a [`Model::update`].
    /// See [`crate::callback`] for details on how to use callbacks.
    ///
    /// # Panics
    /// This function panics if Gurobi errors on clearing the callback.
    pub fn optimize_with_callback<F>(&mut self, callback: &mut F) -> Result<()>
    where
        F: Callback,
    {
        self.update()?;
        let nvars = self.get_attr(attr::NumVars)? as usize;
        let mut usrdata = UserCallbackData {
            model: self,
            cb_obj: callback,
            nvars,
        };

        unsafe {
          let res =
            self.check_apicall(
              ffi::GRBsetcallbackfunc(self.ptr, Some(callback_wrapper), transmute(&mut usrdata), )
            ).and_then(|()| self.check_apicall(
              ffi::GRBoptimize(self.ptr))
          );
          self.check_apicall(
            ffi::GRBsetcallbackfunc(self.ptr, None, null_mut())
          ).expect("failed to clear callback function");
          res
        }
    }

    /// Compute an Irreducible Inconsistent Subsystem (IIS) of the model.  The constraints in the IIS can be identified
    /// by checking their `IISConstr` attribute
    ///
    /// # Example
    /// ```
    /// # use grb::prelude::*;
    ///
    /// fn compute_iis_constraints(m: &mut Model) -> grb::Result<Vec<Constr>> {
    ///    m.compute_iis()?;
    ///    let constrs = m.get_constrs()?; // all constraints in model
    ///    let iis_constrs = m.get_obj_attr_batch(attr::IISConstr, constrs.iter().copied())?
    ///     .into_iter()
    ///     .zip(constrs)
    ///     // IISConstr is 1 if constraint is in the IIS, 0 otherwise
    ///     .filter_map(|(is_iis, c)| if is_iis > 0 { Some(*c)} else { None })
    ///     .collect();
    ///     Ok(iis_constrs)
    /// }
    /// ```
    pub fn compute_iis(&mut self) -> Result<()> {
        self.check_apicall(unsafe { ffi::GRBcomputeIIS(self.ptr) })
    }

    /// Send a request to the model to terminate the current optimization process.
    pub fn terminate(&self) {
        unsafe { ffi::GRBterminate(self.ptr) }
    }

    /// Reset the model to an unsolved state.
    ///
    /// All solution information previously computed are discarded.
    pub fn reset(&self) -> Result<()> {
        self.check_apicall(unsafe { ffi::GRBresetmodel(self.ptr) })
    }

    /// Perform an automated search for parameter settings that improve performance on the model.
    /// See also references [on official
    /// manual](https://www.gurobi.com/documentation/6.5/refman/parameter_tuning_tool.html#sec:Tuning).
    pub fn tune(&self) -> Result<()> {
        self.check_apicall(unsafe { ffi::GRBtunemodel(self.ptr) })
    }

    /// Prepare to retrieve the results of `tune()`.
    /// See also references [on official
    /// manual](https://www.gurobi.com/documentation/6.5/refman/parameter_tuning_tool.html#sec:Tuning).
    pub fn get_tune_result(&self, n: i32) -> Result<()> {
        self.check_apicall(unsafe { ffi::GRBgettuneresult(self.ptr, n) })
    }

    /// Insert a message into log file.
    ///
    /// # Panics
    /// Panics when `message` cannot be converted to a nul-terminated C string.
    pub fn message(&self, message: &str) {
        self.env.message(message);
    }

    /// Import a model from a file. See [`Model::write`](Model::write) for details on valid file types.
    pub fn read(&mut self, filename: &str) -> Result<()> {
        let filename = CString::new(filename)?;
        self.check_apicall(unsafe { ffi::GRBread(self.ptr, filename.as_ptr()) })
    }

    /// Export a model to a file.
    ///
    /// The file type is encoded in the file name suffix. Valid suffixes are `.mps`, `.rew`, `.lp`, or `.rlp` for
    /// writing the model itself, `.ilp` for writing just the IIS associated with an infeasible model,
    /// `.sol` for writing the current solution, `.mst` for writing
    /// a start vector, `.hnt` for writing a hint file, `.bas` for writing an LP basis, `.prm` for writing modified
    /// parameter settings, `.attr` for writing model attributes, or `.json` for writing solution information in
    /// JSON format. If your system has compression utilities installed (e.g., 7z or zip for Windows, and gzip,
    /// bzip2, or unzip for Linux or Mac OS), then the files can be compressed, so additional suffixes of `.gz`,
    /// `.bz2`, or `.7z` are accepted.
    pub fn write(&self, filename: &str) -> Result<()> {
        let filename = CString::new(filename)?;
        self.check_apicall(unsafe { ffi::GRBwrite(self.ptr, filename.as_ptr()) })
    }

    /// Add a decision variable to the model.  This method allows the user to give the entire column (constraint coefficients).
    ///
    /// The [`add_var!`](crate::add_var) macro and its friends are usually easier to use.
    #[allow(clippy::too_many_arguments)]
    pub fn add_var(
        &mut self,
        name: &str,
        vtype: VarType,
        obj: f64,
        lb: f64,
        ub: f64,
        col_coeff: impl IntoIterator<Item = (Constr, f64)>,
    ) -> Result<Var> {
        let name = CString::new(name)?;
        let mut col_coeff = col_coeff.into_iter().peekable();
        let (numnz, _vind, _vval, vind, vval) = if col_coeff.peek().is_some() {
            let (constrs, vals) = self.build_idx_arrays_obj(col_coeff)?;
            let c_ptr = constrs.as_ptr();
            let v_ptr = vals.as_ptr();
            (
                constrs.len() as c_int,
                Some(constrs),
                Some(vals),
                c_ptr,
                v_ptr,
            )
        } else {
            (0, None, None, std::ptr::null(), std::ptr::null())
        };
        self.check_apicall(unsafe {
            ffi::GRBaddvar(
                self.ptr,
                numnz,
                vind,
                vval,
                obj,
                lb,
                ub,
                vtype.into(),
                name.as_ptr(),
            )
        })?;
        Ok(self.vars.add_new(self.update_mode_lazy()?))
    }

    /// Add a Linear constraint to the model.
    ///
    /// The `con` argument is usually created with the [`c!`](crate::c) macro.
    ///
    /// # Examples
    /// ```
    /// # use grb::prelude::*;
    /// let mut m = Model::new("model")?;
    /// let x = add_ctsvar!(m)?;
    /// let y = add_ctsvar!(m)?;
    /// m.add_constr("c1", c!(x <= 1 - y))?;
    /// # Ok::<(), grb::Error>(())
    /// ```
    pub fn add_constr(&mut self, name: &str, con: IneqExpr) -> Result<Constr> where {
        let (lhs, sense, rhs) = con.into_normalised_linear()?;
        let constrname = CString::new(name)?;
        let (vinds, cval) = self.get_coeffs_indices_build(&lhs)?;
        self.check_apicall(unsafe {
            ffi::GRBaddconstr(
                self.ptr,
                cval.len() as ffi::c_int,
                vinds.as_ptr(),
                cval.as_ptr(),
                sense as ffi::c_char,
                rhs,
                constrname.as_ptr(),
            )
        })?;

        Ok(self.constrs.add_new(self.update_mode_lazy()?))
    }

    /// Add multiple linear constraints to the model in a single Gurobi API call.
    ///
    /// Accepts anything that can be turned into an iterator of `(name, constraint)` pairs
    /// where `name : AsRef<str>` (eg `&str` or `String`) and `constraint` is a *linear* [`IneqExpr`].
    ///
    /// # Examples
    /// ```
    /// # use grb::prelude::*;
    /// let mut m = Model::new("model")?;
    /// let x = add_ctsvar!(m)?;
    /// let y = add_ctsvar!(m)?;
    ///
    /// let constraints = vec![
    ///   (&"c1", c!(x <= 1 - y )),
    ///   (&"c2", c!(x == 0.5*y )),
    /// ];
    ///
    /// m.add_constrs(constraints)?;
    ///
    /// // store owned names in Vec to ensure they live long enough
    /// let more_constraints_names : Vec<_> =  (0..10).map(|i| format!("r{}", i)).collect();
    /// // A Map iterator of (&String, IneqConstr)
    /// let more_constraints = (0..10).map(|i| (&more_constraints_names[i], c!(x >= i*y )));
    /// m.add_constrs(more_constraints)?;
    /// # Ok::<(), grb::Error>(())
    /// ```
    ///
    /// # Errors
    /// - [`Error::AlgebraicError`] if a nonlinear constraint is given.
    /// - [`Error::ModelObjectPending`] if some variables haven't yet been added to the model.
    /// - [`Error::ModelObjectRemoved`] if some variables have been removed from the model.
    /// - [`Error::ModelObjectMismatch`] if some variables are from a different model.
    /// - [`Error::FromAPI`] if a Gurobi API error occurs.
    pub fn add_constrs<'a, I, S>(&mut self, constr_with_names: I) -> Result<Vec<Constr>>
    where
        I: IntoIterator<Item = (&'a S, IneqExpr)>,
        S: AsRef<str> + 'a,
    {
        let constr_with_name = constr_with_names.into_iter();
        let (nconstr, _) = constr_with_name.size_hint();
        let mut names = Vec::with_capacity(nconstr); // needed to ensure CString lives long enough
        let mut cnames = Vec::with_capacity(nconstr);
        let mut rhs = Vec::with_capacity(nconstr);
        let mut cbeg = Vec::with_capacity(nconstr);
        let mut cind = Vec::with_capacity(nconstr);
        let mut cval = Vec::with_capacity(nconstr);
        let mut senses = Vec::with_capacity(nconstr);

        let mut c_start = 0;
        for (n, c) in constr_with_name {
            let n = CString::new(n.as_ref())?;
            cnames.push(n.as_ptr());
            names.push(n);
            let (lhs, sense, r) = c.into_normalised_linear()?;
            rhs.push(r);
            senses.push(sense as ffi::c_char);

            let (var_coeff, _) = lhs.into_parts();
            let nterms = var_coeff.len();
            cbeg.push(c_start);
            c_start += nterms as i32;

            cind.reserve(nterms);
            cval.reserve(nterms);
            for (var, coeff) in var_coeff {
                cind.push(self.get_index_build(&var)?);
                cval.push(coeff);
            }
        }

        self.check_apicall(unsafe {
            ffi::GRBaddconstrs(
                self.ptr,
                cnames.len() as ffi::c_int,
                cbeg.len() as ffi::c_int,
                cbeg.as_ptr(),
                cind.as_ptr(),
                cval.as_ptr(),
                senses.as_ptr(),
                rhs.as_ptr(),
                cnames.as_ptr(),
            )
        })?;

        let lazy = self.update_mode_lazy()?;
        Ok(vec![self.constrs.add_new(lazy); cnames.len()])
    }

    /// Add a range constraint to the model.
    ///
    /// This operation adds a decision variable with lower/upper bound, and a linear
    /// equality constraint which states that the value of variable must equal to `expr`.
    ///
    /// As with [`Model::add_constr`], the [`c!`](crate::c) macro is usually used to construct
    /// the second argument.
    ///
    /// # Errors
    /// - [`Error::AlgebraicError`] if the expression in the range constraint is not linear.
    /// - [`Error::ModelObjectPending`] if some variables haven't yet been added to the model.
    /// - [`Error::ModelObjectRemoved`] if some variables have been removed from the model.
    /// - [`Error::ModelObjectMismatch`] if some variables are from a different model.
    /// - [`Error::FromAPI`] if a Gurobi API error occurs.
    ///
    /// # Examples
    /// ```
    /// # use grb::prelude::*;
    /// let mut m = Model::new("model")?;
    /// let x = add_ctsvar!(m)?;
    /// let y = add_ctsvar!(m)?;
    /// m.add_range("", c!(x - y in 0..1))?;
    /// let r = m.add_range("", c!(x*y in 0..1));
    /// assert!(matches!(r, Err(grb::Error::AlgebraicError(_))));
    /// # Ok::<(), grb::Error>(())
    /// ```
    ///
    ///
    pub fn add_range(&mut self, name: &str, expr: RangeExpr) -> Result<(Var, Constr)> {
        let constrname = CString::new(name)?;
        let (expr, lb, ub) = expr.into_normalised()?;
        let (inds, coeff) = self.get_coeffs_indices_build(&expr)?;
        self.check_apicall(unsafe {
            ffi::GRBaddrangeconstr(
                self.ptr,
                coeff.len() as ffi::c_int,
                inds.as_ptr(),
                coeff.as_ptr(),
                lb,
                ub,
                constrname.as_ptr(),
            )
        })?;

        let lazy = self.update_mode_lazy()?;
        let var = self.vars.add_new(lazy);
        let cons = self.constrs.add_new(lazy);
        Ok((var, cons))
    }

    #[allow(unused_variables)]
    /// Add multiple range constraints to the model in a single API call, analagous to
    /// [`Model::add_constrs`].
    ///
    /// # Errors
    /// - [`Error::AlgebraicError`] if the expression a the range constraint is not linear.
    /// - [`Error::ModelObjectPending`] if some variables haven't yet been added to the model.
    /// - [`Error::ModelObjectRemoved`] if some variables have been removed from the model.
    /// - [`Error::ModelObjectMismatch`] if some variables are from a different model.
    /// - [`Error::FromAPI`] if a Gurobi API error occurs.
    pub fn add_ranges<'a, I, N>(&mut self, ranges_with_names: I) -> Result<(Vec<Var>, Vec<Constr>)>
    where
        N: AsRef<str> + 'a,
        I: IntoIterator<Item = (&'a N, RangeExpr)>,
    {
        let ranges_with_names = ranges_with_names.into_iter();
        let (nconstr, _) = ranges_with_names.size_hint();
        let mut names = Vec::with_capacity(nconstr); // needed to ensure CString lives long enough
        let mut cnames = Vec::with_capacity(nconstr);
        let mut ubs = Vec::with_capacity(nconstr);
        let mut lbs = Vec::with_capacity(nconstr);
        let mut cbeg = Vec::with_capacity(nconstr);
        let mut cind = Vec::with_capacity(nconstr);
        let mut cval = Vec::with_capacity(nconstr);

        let mut c_start = 0;
        for (n, r) in ranges_with_names {
            let n = CString::new(n.as_ref())?;
            cnames.push(n.as_ptr());
            names.push(n);
            let (expr, lb, ub) = r.into_normalised()?;
            ubs.push(ub);
            lbs.push(lb);

            let (var_coeff, _) = expr.into_parts();
            let nterms = var_coeff.len();
            cbeg.push(c_start);
            c_start += nterms as i32;

            cind.reserve(nterms);
            cval.reserve(nterms);
            for (var, coeff) in var_coeff {
                cind.push(self.get_index_build(&var)?);
                cval.push(coeff);
            }
        }

        self.check_apicall(unsafe {
            ffi::GRBaddrangeconstrs(
                self.ptr,
                cnames.len() as ffi::c_int,
                cbeg.len() as ffi::c_int,
                cbeg.as_ptr(),
                cind.as_ptr(),
                cval.as_ptr(),
                lbs.as_ptr(),
                ubs.as_ptr(),
                cnames.as_ptr(),
            )
        })?;

        let ncons = names.len();
        let lazy = self.update_mode_lazy()?;
        let vars = vec![self.vars.add_new(lazy); ncons];
        let cons = vec![self.constrs.add_new(lazy); ncons];
        Ok((vars, cons))
    }

    /// Add a quadratic constraint to the model.  See the [manual](https://www.gurobi.com/documentation/9.1/refman/c_addqconstr.html)
    /// for which quadratic expressions are accepted by Gurobi.
    ///
    /// # Errors
    /// - [`Error::ModelObjectPending`] if some variables haven't yet been added to the model.
    /// - [`Error::ModelObjectRemoved`] if some variables have been removed from the model.
    /// - [`Error::ModelObjectMismatch`] if some variables are from a different model.
    /// - [`Error::FromAPI`] if a Gurobi API error occurs.
    pub fn add_qconstr(&mut self, name: &str, constraint: IneqExpr) -> Result<QConstr> {
        let (lhs, sense, rhs) = constraint.into_normalised_quad();
        let cname = CString::new(name)?;
        let (qrow, qcol, qval) = self.get_qcoeffs_indices_build(&lhs)?;
        let (_, lexpr) = lhs.into_parts();
        let (lvar, lval) = self.get_coeffs_indices_build(&lexpr)?;
        self.check_apicall(unsafe {
            ffi::GRBaddqconstr(
                self.ptr,
                lval.len() as ffi::c_int,
                lvar.as_ptr(),
                lval.as_ptr(),
                qval.len() as ffi::c_int,
                qrow.as_ptr(),
                qcol.as_ptr(),
                qval.as_ptr(),
                sense as ffi::c_char,
                rhs,
                cname.as_ptr(),
            )
        })?;

        Ok(self.qconstrs.add_new(self.update_mode_lazy()?))
    }

    /// Add a single [Special Order Set (SOS)](https://www.gurobi.com/documentation/9.1/refman/constraints.html#subsubsection:SOSConstraints)
    /// constraint to the model.
    ///
    /// # Errors
    /// - [`Error::ModelObjectPending`] if some variables haven't yet been added to the model.
    /// - [`Error::ModelObjectRemoved`] if some variables have been removed from the model.
    /// - [`Error::ModelObjectMismatch`] if some variables are from a different model.
    /// - [`Error::FromAPI`] if a Gurobi API error occurs.
    pub fn add_sos(
        &mut self,
        var_weight_pairs: impl IntoIterator<Item = (Var, f64)>,
        sostype: SOSType,
    ) -> Result<SOS> {
        let var_weight_pairs = var_weight_pairs.into_iter();
        let n = var_weight_pairs.size_hint().0;
        let mut ind = Vec::with_capacity(n);
        let mut weight = Vec::with_capacity(n);

        for (var, w) in var_weight_pairs {
            ind.push(self.get_index_build(&var)?);
            weight.push(w)
        }

        let beg = 0;
        let sostype = sostype as c_int;
        self.check_apicall(unsafe {
            ffi::GRBaddsos(
                self.ptr,
                1,
                ind.len() as ffi::c_int,
                &sostype,
                &beg,
                ind.as_ptr(),
                weight.as_ptr(),
            )
        })?;

        Ok(self.sos.add_new(self.update_mode_lazy()?))
    }

    /// Set the objective function of the model and optimisation direction (min or max).
    /// Because this requires setting a [`Var`] attribute (the `Obj` attribute), this method
    /// always triggers a model update.
    ///
    /// # Errors
    /// - [`Error::ModelObjectPending`] if some variables haven't yet been added to the model.
    /// - [`Error::ModelObjectRemoved`] if some variables have been removed from the model.
    /// - [`Error::ModelObjectMismatch`] if some variables are from a different model.
    /// - [`Error::FromAPI`] if a Gurobi API error occurs.
    pub fn set_objective(&mut self, expr: impl Into<Expr>, sense: ModelSense) -> Result<()> {
        self.update()?;
        let expr: Expr = expr.into();
        self.del_qpterms()?;

        let expr = if expr.is_linear() {
            expr.into_linexpr().unwrap()
        } else {
            let qexpr = expr.into_quadexpr();
            let (qrow, qcol, qval) = self.get_qcoeffs_indices_build(&qexpr)?;
            self.add_qpterms(&qrow, &qcol, &qval)?;
            let (_, expr) = qexpr.into_parts();
            expr
        };

        let (coeff_map, obj_cons) = expr.into_parts();

        self.set_obj_attr_batch(attr::Obj, coeff_map)?;
        self.set_attr(attr::ObjCon, obj_cons)?;
        self.set_attr(attr::ModelSense, sense)
    }

    /// Get a constraint by name.  Returns either a constraint if one was found, or `None` if none were found.
    /// If multiple constraints match, the method returns an arbitary one.
    ///
    /// # Usage
    /// ```
    /// # use grb::prelude::*;
    /// let mut m = Model::new("model")?;
    /// let x = add_binvar!(m)?;
    /// let y = add_binvar!(m)?;
    /// let c = m.add_constr("constraint", c!(x + y == 1))?;
    /// assert_eq!(m.get_constr_by_name("constraint").unwrap_err(), grb::Error::ModelUpdateNeeded);
    /// m.update()?;
    /// assert_eq!(m.get_constr_by_name("constraint")?, Some(c));
    /// assert_eq!(m.get_constr_by_name("foo")?, None);
    /// # Ok::<(), grb::Error>(())
    /// ```
    ///
    /// # Errors
    /// - [`Error::NulError`] if the `name` cannot be converted to a C-string
    /// - [`Error::ModelUpdateNeeded`] if a model update is needed.
    /// - [`Error::ModelObjectRemoved`] if the constraint has been removed from the model.
    /// - [`Error::ModelObjectMismatch`] if the constraint is from a different model.
    /// - [`Error::FromAPI`] if a Gurobi API error occurs.
    pub fn get_constr_by_name(&self, name: &str) -> Result<Option<Constr>> {
        if self.constrs.model_update_needed() {
            return Err(Error::ModelUpdateNeeded);
        }
        let n = CString::new(name)?;
        let mut idx = i32::min_value();
        self.check_apicall(unsafe { ffi::GRBgetconstrbyname(self.ptr, n.as_ptr(), &mut idx) })?;
        if idx < 0 {
            Ok(None)
        } else {
            Ok(Some(self.constrs.objects()[idx as usize])) // should only panic if there's a bug in IdxManager
        }
    }

    /// Get a variable object by name.  See [`Model::get_constr_by_name`] for details
    ///
    /// # Errors
    /// - [`Error::NulError`] if the `name` cannot be converted to a C-string
    /// - [`Error::ModelUpdateNeeded`] if a model update is needed.
    /// - [`Error::ModelObjectRemoved`] if the variable has been removed from the model.
    /// - [`Error::ModelObjectMismatch`] if the variable is from a different model.
    /// - [`Error::FromAPI`] if a Gurobi API error occurs.
    pub fn get_var_by_name(&self, name: &str) -> Result<Option<Var>> {
        if self.vars.model_update_needed() {
            return Err(Error::ModelUpdateNeeded);
        }
        let n = CString::new(name)?;
        let mut idx = i32::min_value();
        self.check_apicall(unsafe { ffi::GRBgetvarbyname(self.ptr, n.as_ptr(), &mut idx) })?;
        if idx < 0 {
            Ok(None)
        } else {
            Ok(Some(self.vars.objects()[idx as usize])) // should only panic if there's a bug in IdxManager
        }
    }

    /// Query a Model attribute.    Model attributes (objects with the `ModelAttr` trait) can be found in the [`attr`] module.
    pub fn get_attr<A: ModelAttrGet<V>, V>(&self, attr: A) -> Result<V> {
        attr.get(self)
    }

    /// Query a model object attribute (Constr, Var, etc).  Available attributes can be found
    /// in the [`attr`] module, which is imported in the [prelude](crate::prelude).
    pub fn get_obj_attr<A, O, V>(&self, attr: A, obj: &O) -> Result<V>
    where
        A: ObjAttrGet<O, V>,
        O: ModelObject,
    {
        attr.get(self, self.get_index(obj)?)
    }

    /// Query an attribute of multiple model objects.   Available attributes can be found
    /// in the [`attr`] module, which is imported in the [prelude](crate::prelude).
    pub fn get_obj_attr_batch<A, I, O, V>(&self, attr: A, objs: I) -> Result<Vec<V>>
    where
        A: ObjAttrGet<O, V>,
        I: IntoIterator<Item = O>,
        O: ModelObject,
    {
        attr.get_batch(self, objs.into_iter().map(|obj| self.get_index(&obj)))
    }

    /// Set a model attribute.  Attributes (objects with the `Attr` trait) can be found in the [`attr`] module.
    ///
    /// # Example
    /// ```
    /// # use grb::prelude::*;
    /// let mut model = Model::new("")?;
    /// model.set_attr(attr::ModelName, "model".to_string())?;
    /// # Ok::<(), grb::Error>(())
    /// ```
    pub fn set_attr<A: ModelAttrSet<V>, V>(&self, attr: A, value: V) -> Result<()> {
        attr.set(self, value)
    }

    /// Set an attribute of a Model object (Const, Var, etc).   Attributes (objects with the `Attr` trait) can be found
    /// in the [`attr`] module.
    ///
    /// # Example
    /// ```
    /// # use grb::prelude::*;
    /// let mut model = Model::new("")?;
    /// let x = add_ctsvar!(model)?;
    /// let c = model.add_constr("", c!(x <= 1))?;
    /// model.set_obj_attr(attr::VarName, &x, "x")?;
    /// model.set_obj_attr(attr::ConstrName, &c, "c")?;
    /// # Ok::<(), grb::Error>(())
    /// ```
    /// Trying to set an attribute on a model object that belongs to another model object type will fail to compile:
    /// ```compile_fail
    /// # use grb::prelude::*;
    /// # let mut model = Model::new("")?;
    /// # let x = add_ctsvar!(model)?;
    /// model.set_obj_attr2(attr::ConstrName, &x, "c")?;
    /// # Ok::<(), grb::Error>(())
    /// ```
    pub fn set_obj_attr<A, O, V>(&self, attr: A, obj: &O, val: V) -> Result<()>
    where
        A: ObjAttrSet<O, V>,
        O: ModelObject,
    {
        attr.set(self, self.get_index_build(obj)?, val)
    }

    /// Set an attribute of multiple Model objects (Const, Var, etc).   Attributes (objects with the `Attr` trait) can be
    /// found in the [`attr`] module.
    pub fn set_obj_attr_batch<A, O, I, V>(&self, attr: A, obj_val_pairs: I) -> Result<()>
    where
        A: ObjAttrSet<O, V>,
        I: IntoIterator<Item = (O, V)>,
        O: ModelObject,
    {
        attr.set_batch(
            self,
            obj_val_pairs
                .into_iter()
                .map(|(obj, val)| (self.get_index(&obj), val)),
        )
    }

    /// Set a model parameter.  Parameters (objects with the `Param` trait) can be found in the [`param`] module.
    ///
    /// # Example
    /// ```
    /// # use grb::prelude::*;
    /// let mut model = Model::new("")?;
    /// model.set_param(param::OutputFlag, 0)?;
    /// # Ok::<(), grb::Error>(())
    /// ```
    pub fn set_param<P: ParamSet<V>, V>(&mut self, param: P, value: V) -> Result<()> {
        self.get_env_mut().set(param, value)
    }

    /// Query a model parameter.  Parameters (objects with the `Param` trait) can be found in the [`param`] module.
    ///
    /// # Example
    /// ```
    /// # use grb::prelude::*;
    /// let mut model = Model::new("")?;
    /// assert_eq!(model.get_param(param::LazyConstraints)?, 0);
    /// # Ok::<(), grb::Error>(())
    /// ```
    pub fn get_param<P: ParamGet<V>, V>(&self, param: P) -> Result<V> {
        self.get_env().get(param)
    }

    /// Modify the model to create a feasibility relaxation.
    ///
    /// Given a `Model` whose objective function is $f(x)$, the feasibility relaxation seeks to minimise
    /// $$
    ///   \text{min}\quad f(x) + \sum_{j} w_j \cdot p(s_j)
    /// $$
    /// where $s\_j > 0$ is the slack variable of $j$ -th constraint or bound, $w_j$ is the $j$-th weight
    /// and $p(s)$ is the penalty function.
    ///
    /// The `ty` argument sets the penalty function:
    ///
    /// | [`RelaxType`] variant | Penalty function                                                                    |
    /// | --------------------- | ----------------------------------------------------------------------------------- |
    /// | `Quadratic`           | $ p(s) = {s}^2 $                                                                    |
    /// | `Linear`              | $ p(s) = {s} $                                                                      |
    /// | `Cardinality`         | $ p(s) = \begin{cases} 1 & \text{if } s > 0 \\\\ 0 & \text{otherwise} \end{cases} $ |
    ///
    /// This method will modify the model - if this is not desired copy the model before invoking
    /// this method with [`Model::try_clone()`].
    ///
    /// ## Arguments
    /// * `ty` : The type of cost function used when finding the minimum cost relaxation.
    /// * `minrelax` : How the objective should be minimised.
    ///
    ///   If `false`, optimizing the returned model gives a solution that minimizes the cost of the
    ///   violation. If `true`, optimizing the returned model finds a solution that minimizes the original objective,
    ///   but only from among those solutions that minimize the cost of the violation. Note that this method must solve an
    ///   optimization problem to find the minimum possible relaxation when set to `true`, which can be quite expensive.
    ///
    /// * `lb_pen` : Variables whose lower bounds are allowed to be violated, and their penalty weights.
    /// * `ub_pen` : Variables whose upper bounds are allowed to be violated, and their penalty weights.
    /// * `constr_pen` :  Constraints which are allowed to be violated, and their penalty weights.
    ///
    /// ## Returns
    /// * The objective value for the relaxation performed (if `minrelax` is `true`).
    /// * Slack variables for relaxation and related linear/quadratic constraints.
    #[allow(clippy::type_complexity)]
    #[allow(clippy::too_many_arguments)]
    pub fn feas_relax(
        &mut self,
        ty: RelaxType,
        minrelax: bool,
        lb_pen: impl IntoIterator<Item = (Var, f64)>,
        ub_pen: impl IntoIterator<Item = (Var, f64)>,
        constr_pen: impl IntoIterator<Item = (Constr, f64)>,
    ) -> Result<(Option<f64>, Vec<Var>, Vec<Constr>, Vec<QConstr>)> {
        self.update()?;
        let n_old_vars = self.get_attr(attr::NumVars)? as usize;
        let n_old_constr = self.get_attr(attr::NumConstrs)? as usize;
        let n_old_qconstr = self.get_attr(attr::NumQConstrs)? as usize;

        fn build_array<T, O>(
            model: &Model,
            iter: T,
            buf_size: usize,
        ) -> Result<(Option<Vec<f64>>, *const f64)>
        where
            T: IntoIterator<Item = (O, f64)>,
            O: ModelObject,
        {
            let mut iter = iter.into_iter().peekable();
            if iter.peek().is_some() {
                let mut vec = vec![super::INFINITY; buf_size];
                for (obj, weight) in iter {
                    vec[model.get_index(&obj)? as usize] = weight;
                }
                let ptr = vec.as_ptr();
                Ok((Some(vec), ptr))
            } else {
                Ok((None, std::ptr::null()))
            }
        }

        let (_lbpen, lbpen) = build_array(self, lb_pen, n_old_vars)?;
        let (_ubpen, ubpen) = build_array(self, ub_pen, n_old_vars)?;
        let (_rhspen, rhspen) = build_array(self, constr_pen, n_old_constr)?;

        let mut feasobj = crate::constants::GRB_UNDEFINED;
        self.check_apicall(unsafe {
            ffi::GRBfeasrelax(
                self.ptr,
                ty as c_int,
                minrelax as c_int,
                lbpen,
                ubpen,
                rhspen,
                &mut feasobj,
            )
        })?;
        let feasobj = if minrelax { Some(feasobj) } else { None };
        self.update()?;

        let lazy = self.update_mode_lazy()?;

        let n_vars = self.get_attr(attr::NumVars)? as usize;
        assert!(n_vars >= n_old_vars);
        let new_vars = (0..n_vars - n_old_vars)
            .map(|_| self.vars.add_new(lazy))
            .collect();

        let n_cons = self.get_attr(attr::NumConstrs)? as usize;
        assert!(n_cons >= n_old_constr);
        let new_cons = (0..n_cons - n_old_constr)
            .map(|_| self.constrs.add_new(lazy))
            .collect();

        let n_qcons = self.get_attr(attr::NumQConstrs)? as usize;
        assert!(n_qcons >= n_old_qconstr);
        let new_qcons = (0..n_cons - n_old_constr)
            .map(|_| self.qconstrs.add_new(lazy))
            .collect();

        Ok((feasobj, new_vars, new_cons, new_qcons))
    }

    /// Set a piecewise-linear objective function for the variable.
    ///
    /// Given a sequence of points $(x_1, y_1), \dots, (x_n, y_n)$, the piecewise-linear objective function
    /// $f(x)$ is defined as follows:
    /// $$
    ///   f(x) =
    ///   \begin{cases}
    ///     y_1 + \dfrac{y_2 - y_1}{x_2 - x_1} \\, (x - x_1)         & \text{if $x \leq x_1$}, \\\\
    ///   \\\\
    ///     y_i + \dfrac{y_{i+1} - y_i}{x_{i+1}-x_i} \\, (x - x_i)   & \text{if $x_i \leq x \leq x_{i+1}$}, \\\\
    ///   \\\\
    ///     y_n + \dfrac{y_n - y_{n-1}}{x_n-x_{n-1}} \\, (x - x_n)   & \text{if $x \geq x_n$},
    ///   \end{cases}
    /// $$
    ///
    /// The `Obj` attribute of the [`Var`] object will be set to 0.   To delete the piecewise-linear function on the
    /// variable, set the value of `Obj` attribute to non-zero.
    ///
    /// The `points` argument contains the pairs $(x_i,y_i)$ and must satisfy $x_i < x_{i+1}$.
    pub fn set_pwl_obj(
        &mut self,
        var: &Var,
        points: impl IntoIterator<Item = (f64, f64)>,
    ) -> Result<()> {
        let points = points.into_iter();
        let n = points.size_hint().0;
        let mut xvals = Vec::with_capacity(n);
        let mut yvals = Vec::with_capacity(n);
        for (x, y) in points {
            xvals.push(x);
            yvals.push(y);
        }

        self.check_apicall(unsafe {
            ffi::GRBsetpwlobj(
                self.ptr,
                self.get_index_build(var)?,
                xvals.len() as ffi::c_int,
                xvals.as_ptr(),
                yvals.as_ptr(),
            )
        })
    }

    /// Retrieve the status of the model.
    pub fn status(&self) -> Result<Status> {
        self.get_attr(attr::Status)
    }

    impl_object_list_getter!(get_vars, Var, vars, "variables");

    impl_object_list_getter!(get_constrs, Constr, constrs, "constraints");

    impl_object_list_getter!(get_qconstrs, QConstr, qconstrs, "quadratic constraints");

    impl_object_list_getter!(get_sos, SOS, sos, "SOS constraints");

    /// Remove a variable or constraint from the model.
    pub fn remove<O: ModelObject>(&mut self, item: O) -> Result<()> {
        let lazy = self.update_mode_lazy()?;
        let im = O::idx_manager_mut(self);
        let idx = im.get_index(&item)?;
        im.remove(item, lazy)?;
        self.check_apicall(unsafe { O::gurobi_remove(self.ptr, &[idx]) })
    }

    /// Retrieve a single constant matrix coefficient of the model.
    pub fn get_coeff(&self, var: &Var, constr: &Constr) -> Result<f64> {
        let mut value = 0.0;
        self.check_apicall(unsafe {
            ffi::GRBgetcoeff(
                self.ptr,
                self.get_index_build(constr)?,
                self.get_index_build(var)?,
                &mut value,
            )
        })?;
        Ok(value)
    }

    /// Change a single constant matrix coefficient of the model.
    pub fn set_coeff(&mut self, var: &Var, constr: &Constr, value: f64) -> Result<()> {
        self.check_apicall(unsafe {
            ffi::GRBchgcoeffs(
                self.ptr,
                1,
                &self.get_index_build(constr)?,
                &self.get_index_build(var)?,
                &value,
            )
        })
    }

    /// Change a set of constant matrix coefficients of the model.
    pub fn set_coeffs(
        &mut self,
        coeffs: impl IntoIterator<Item = (Var, Constr, f64)>,
    ) -> Result<()> {
        let (vind, cind, val) = self.build_idx_arrays_obj_obj(coeffs.into_iter())?;
        self.check_apicall(unsafe {
            ffi::GRBchgcoeffs(
                self.as_mut_ptr(),
                vind.len() as ffi::c_int,
                cind.as_ptr(),
                vind.as_ptr(),
                val.as_ptr(),
            )
        })
    }

    // add quadratic terms of objective function.
    fn add_qpterms(&mut self, qrow: &[i32], qcol: &[i32], qval: &[f64]) -> Result<()> {
        self.check_apicall(unsafe {
            ffi::GRBaddqpterms(
                self.ptr,
                qrow.len() as ffi::c_int,
                qrow.as_ptr(),
                qcol.as_ptr(),
                qval.as_ptr(),
            )
        })
    }

    // remove quadratic terms of objective function.
    fn del_qpterms(&mut self) -> Result<()> {
        self.check_apicall(unsafe { ffi::GRBdelq(self.ptr) })
    }

    pub(crate) fn check_apicall(&self, error: ffi::c_int) -> Result<()> {
        if error != 0 {
            return Err(self.env.error_from_api(error));
        }
        Ok(())
    }
}

impl Drop for Model {
    fn drop(&mut self) {
        // Note: This method runs *before* the `drop()` method on the env inside the model
        // so we free the GRBModel before the GRBEnv, as per the Gurobi docs.
        unsafe { ffi::GRBfreemodel(self.ptr) };
    }
}

/// A handle to an [`AsyncModel`](crate::model::AsyncModel) which is currently solving.
pub struct AsyncHandle(Model);

impl AsyncHandle {
    /// Retrieve current the `attr::Status` of the model.
    pub fn status(&self) -> Result<Status> {
        self.0.status()
    }

    /// Retrieve the current `attr::ObjVal` of the model.
    pub fn obj_val(&self) -> Result<f64> {
        self.0.get_attr(attr::ObjVal)
    }

    /// Retrieve the current  `attr::ObjBound` of the model.
    pub fn obj_bnd(&self) -> Result<f64> {
        self.0.get_attr(attr::ObjBound)
    }

    /// Retrieve the current `attr::IterCount` of the model.
    pub fn iter_cnt(&self) -> Result<f64> {
        self.0.get_attr(attr::IterCount)
    }

    /// Retrieve the current `attr::BarIterCount` of the model.
    pub fn bar_iter_cnt(&self) -> Result<i32> {
        self.0.get_attr(attr::BarIterCount)
    }

    /// Retrieve the current `attr::NodeCount` of the model.
    pub fn node_cnt(&self) -> Result<f64> {
        self.0.get_attr(attr::NodeCount)
    }

    /// Wait for optimisation to finish.
    ///
    /// # Errors
    /// An [`Error::FromAPI`] may occur during optimisation, in which case it is stored in the `Result`.
    pub fn join(self) -> (AsyncModel, Result<()>) {
        let errors = self
            .0
            .check_apicall(unsafe { ffi::GRBsync(self.0.ptr) });
        (AsyncModel(self.0), errors)
    }

    /// Send a request to Gurobi to terminate optimization.  Optimization may not finish immediately.
    ///
    /// # Example
    /// ```
    /// # use grb::prelude::*;
    /// use grb::AsyncModel;
    /// let e = Env::new("")?;
    /// let m = Model::with_env("async", e)?;
    /// # /*
    ///   ...
    /// # */
    /// let m = AsyncModel::new(m);
    /// // discard `AsyncModel` on failure and panic
    /// let handle = m.optimize().map_err(|(_, e)| e).unwrap();
    /// # /*
    ///   ...
    /// # */
    /// handle.terminate();
    /// # let (m, errors) = handle.join();
    /// # errors?;
    /// # Ok::<(), grb::Error>(())
    /// ```
    pub fn terminate(&self) {
        self.0.terminate()
    }
}

/// A wrapper around [`Model`] that supports async optimisation in the background.
///
///  From the Gurobi [manual](https://www.gurobi.com/documentation/9.1/refman/c_optimizeasync.html), regarding solving models asynchronously:
///
/// *"[modifying or performing non-permitted] calls on the running model, **or on any other models that were built within the same Gurobi environment**,
///  will fail with error code `OPTIMIZATION_IN_PROGRESS`."*
///
/// For this reason, creating an `AsyncModel` requires a [`Model`] whose [`Env`] wasn't previously been used to construct other models.
///
/// `Model` implements `From<AsyncModel>`, so you can recover the `Model` using `.into()` (see examples).
pub struct AsyncModel(Model);

impl AsyncModel {
    /// # Panics
    /// This function will panic if the `model` does not have sole ownership over its `Env`.  This means
    /// the `Model` cannot be created with [`Model::new`], instead you must use [`Model::with_env`].
    /// # Examples
    ///
    /// This example panics because `env` has two references - inside `m` and the bound variable in the current scope
    /// ```should_panic
    /// use grb::prelude::*;
    /// use grb::AsyncModel;
    ///
    /// let env = Env::new("")?;
    /// let mut m = Model::with_env("model", &env)?;
    /// let mut m =  AsyncModel::new(m); // panic - env is still in scope
    /// # Ok::<(), grb::Error>(())
    /// ```
    /// This is easily resolved by ensuring `env` is no longer in scope when the `AsyncModel` is created.
    /// ```
    /// # use grb::prelude::*;
    /// # use grb::AsyncModel;
    /// # let env = Env::new("")?;
    /// let mut m = Model::with_env("model", &env)?;
    /// drop(env);
    /// let mut m =  AsyncModel::new(m); // ok
    /// # Ok::<(), grb::Error>(())
    /// ```
    /// You can also pass an owned `Env` to `Model::with_env`:
    /// ```
    /// # use grb::prelude::*;
    /// # use grb::AsyncModel;
    /// # let env = Env::new("")?;
    /// let mut m = Model::with_env("model", env)?;
    /// let mut m =  AsyncModel::new(m); // also ok
    /// # Ok::<(), grb::Error>(())
    /// ```
    /// This example panics because `m` uses the default `Env`, which is also stored globally.
    /// `Model`s created with [`Model::new`] can never be made into `AsyncModel`s for this reason.
    /// ```should_panic
    /// # use grb::prelude::*;
    /// # use grb::AsyncModel;
    /// let m = Model::new("model1")?;
    /// let m =  AsyncModel::new(m); // panic
    /// # Ok::<(), grb::Error>(())
    /// ```
    ///
    pub fn new(model: Model) -> AsyncModel {
        if model.env.is_shared() {
            panic!("Cannot create async model - environment is used in other models");
        }
        AsyncModel(model)
    }

    /// Optimize the model on another thread.  This method will always trigger a [`Model::update`] on the underlying `Model`.
    ///
    /// On success, returns an [`AsyncHandle`](crate::model::AsyncHandle) that provides a limited API for model queries.
    /// The `AsyncModel` can be retrieved by calling [`AsyncHandle::join`](crate::model::AsyncHandle::join).
    ///
    /// # Errors
    /// An `grb::Error::FromAPI` may occur.  In this case, the `Err` variant contains this error
    /// and gives back ownership of this `AsyncModel`.
    ///
    /// # Examples
    /// ```
    /// use grb::prelude::*;
    /// use grb::AsyncModel;
    ///
    /// let mut m = Model::with_env("model", &Env::new("")?)?;
    /// let x = add_ctsvar!(m, obj: 2)?;
    /// let y = add_intvar!(m, bounds: 0..100)?;
    /// m.add_constr("c0", c!(x <= y - 0.5 ))?;
    /// let m = AsyncModel::new(m);
    ///
    /// let handle = match m.optimize() {
    ///   Err((_, e)) => panic!("{}", e),
    ///   Ok(h) => h
    /// };
    ///
    /// println!("The model has explored {} MIP nodes so far", handle.node_cnt()?);
    /// let (m, errors) = handle.join(); // the AsyncModel is always returned
    /// errors?; // optimisation errors - as if Model::optimize were called.
    /// let m: Model = m.into(); // get original Model back
    /// # Ok::<(), grb::Error>(())
    /// ```
    pub fn optimize(mut self) -> std::result::Result<AsyncHandle, (Self, Error)> {
        match self.0.update().and_then(|_| {
            self.0
                .check_apicall(unsafe { ffi::GRBoptimizeasync(self.0.ptr) })
        }) {
            Ok(()) => Ok(AsyncHandle(self.0)),
            Err(e) => Err((self, e)),
        }
    }
}

// TODO: check that multi-objective and scenario optimisation work/are usable

impl std::convert::From<AsyncModel> for Model {
    fn from(model: AsyncModel) -> Model {
        model.0
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    extern crate self as grb;

    #[test]
    fn model_id_factory() {
        let mut env = Env::new("").unwrap();
        env.set(param::OutputFlag, 0).unwrap();

        let mut m1 = Model::with_env("test1", &env).unwrap();
        let mut m2 = Model::with_env("test2", &env).unwrap();

        let x1 = add_var!(m1, Binary,  name:"x1").unwrap();
        let x2 = add_var!(m2, Binary,  name:"x2").unwrap();
        assert_ne!(m1.id, m2.id);

        assert_eq!(x1.model_id, m1.id);
        assert_eq!(x1.id, 0);
        assert_eq!(x2.model_id, m2.id);
        assert_eq!(x2.id, 0);
    }

    #[test]
    fn eager_update() {
        let mut model = Model::new("test").unwrap();
        assert_eq!(model.get_param(param::UpdateMode).unwrap(), 1);
        assert!(!model.update_mode_lazy().unwrap());
        let x = add_binvar!(model, name:"x").unwrap();
        let y = add_binvar!(model, name:"y").unwrap();
        let c1 = model.add_constr("c1", c!(x + y <= 1)).unwrap(); // should work fine

        assert_eq!(model.get_attr(attr::NumVars).unwrap(), 0);
        assert!(model.get_index(&x).is_err());
        assert_eq!(model.get_index_build(&x).unwrap(), 0);
        assert!(model.get_index(&y).is_err());
        assert_eq!(model.get_index_build(&y).unwrap(), 1);
        assert_eq!(model.get_index_build(&c1).unwrap(), 0);

        model.update().unwrap();
        assert_eq!(model.get_attr(attr::NumVars).unwrap(), 2);
        assert_eq!(model.get_index(&x).unwrap(), 0);
        assert_eq!(model.get_index(&y).unwrap(), 1);

        let z = add_binvar!(model, name:"z").unwrap();
        assert_eq!(model.get_attr(attr::NumVars).unwrap(), 2);
        assert_eq!(model.get_index(&x).unwrap(), 0);
        assert_eq!(model.get_index(&y).unwrap(), 1);
        assert!(model.get_index(&z).is_err());
        assert_eq!(model.get_index_build(&z).unwrap(), 2);

        model.remove(y).unwrap();
        let c2 = model.add_constr("c2", c!(z + y <= 1)).unwrap(); // I know it's weird, because y is removed, but that's what Gurobi does
        assert_eq!(model.get_index_build(&c2).unwrap(), 1);
        assert_eq!(model.get_attr(attr::NumVars).unwrap(), 2);
        assert_eq!(model.get_index(&x).unwrap(), 0);
        assert_eq!(model.get_index(&y), Err(Error::ModelObjectRemoved)); // No longer available
        assert!(model.get_index(&z).is_err());
        assert_eq!(model.get_index_build(&z).unwrap(), 2);

        let w = add_binvar!(model, name:"w").unwrap();
        assert_eq!(model.get_attr(attr::NumVars).unwrap(), 2);
        assert_eq!(model.get_index(&x).unwrap(), 0);
        assert_eq!(model.get_index(&y), Err(Error::ModelObjectRemoved)); // No longer available
        assert!(model.get_index(&z).is_err());
        assert_eq!(model.get_index_build(&z).unwrap(), 2);
        assert!(model.get_index(&w).is_err());
        assert_eq!(model.get_index_build(&w).unwrap(), 3);

        model.update().unwrap();
        assert_eq!(model.get_attr(attr::NumVars).unwrap(), 3);
        assert_eq!(model.get_attr(attr::NumNZs).unwrap(), 2); // start with 2 constraints with 2 vars = 2 x 2 = 4, minus where y appears twice = 4 - 2 = 2
        assert_eq!(model.get_index(&x).unwrap(), 0);
        assert!(model.get_index(&y).is_err());
        assert_eq!(model.get_index(&z).unwrap(), 1);
        assert_eq!(model.get_index(&w).unwrap(), 2);

        assert_eq!(
            model.get_obj_attr(attr::VarName, &x).unwrap(),
            "x".to_string()
        );
        assert_eq!(
            model.get_obj_attr(attr::VarName, &z).unwrap(),
            "z".to_string()
        );
        assert_eq!(
            model.get_obj_attr(attr::VarName, &w).unwrap(),
            "w".to_string()
        );
    }

    #[test]
    fn lazy_update() {
        let mut env = Env::new("").unwrap();
        env.set(param::OutputFlag, 0).unwrap();
        env.set(param::UpdateMode, 0).unwrap();
        let mut model = Model::with_env("bug", &env).unwrap();
        assert!(model.update_mode_lazy().unwrap());

        let x = add_binvar!(model, name:"x").unwrap();
        let y = add_binvar!(model, name:"y").unwrap();
        assert_eq!(model.get_attr(attr::NumVars).unwrap(), 0);
        assert_eq!(
            model.get_index_build(&x).unwrap_err(),
            Error::ModelObjectPending
        );
        assert_eq!(
            model.get_index_build(&y).unwrap_err(),
            Error::ModelObjectPending
        );
        model.add_constr("c1", c!(x + y <= 1)).unwrap_err();

        model.update().unwrap();
        assert_eq!(model.get_attr(attr::NumVars).unwrap(), 2);
        assert_eq!(model.get_index(&x).unwrap(), 0);
        assert_eq!(model.get_index(&y).unwrap(), 1);
        let c1 = model.add_constr("c1", c!(x + y <= 1)).unwrap();

        model.remove(y).unwrap();
        let z = add_binvar!(model, name:"z").unwrap();
        let w = add_binvar!(model, name:"w").unwrap();
        assert_eq!(model.get_attr(attr::NumVars).unwrap(), 2);
        assert_eq!(model.get_index(&x).unwrap(), 0);
        assert_eq!(model.get_index(&y).unwrap_err(), Error::ModelObjectRemoved); // this is updated instantly
        assert_eq!(
            model.get_index_build(&z).unwrap_err(),
            Error::ModelObjectPending
        );
        assert_eq!(
            model.get_index_build(&w).unwrap_err(),
            Error::ModelObjectPending
        );

        model.update().unwrap();
        assert_eq!(model.get_attr(attr::NumVars).unwrap(), 3);
        assert_eq!(model.get_index(&x).unwrap(), 0);
        assert_eq!(model.get_index(&y).unwrap_err(), Error::ModelObjectRemoved);
        assert_eq!(model.get_index(&z).unwrap(), 1);
        assert_eq!(model.get_index(&w).unwrap(), 2);
        assert_eq!(model.get_index(&c1).unwrap(), 0);

        assert_eq!(
            model.get_obj_attr(attr::VarName, &x).unwrap(),
            "x".to_string()
        );
        assert_eq!(
            model.get_obj_attr(attr::VarName, &z).unwrap(),
            "z".to_string()
        );
        assert_eq!(
            model.get_obj_attr(attr::VarName, &w).unwrap(),
            "w".to_string()
        );
    }

    #[test]
    fn model_obj_size() {
        assert_eq!(std::mem::size_of::<Var>(), 8);
        assert_eq!(std::mem::size_of::<QConstr>(), 8);
        assert_eq!(std::mem::size_of::<Constr>(), 8);
        assert_eq!(std::mem::size_of::<SOS>(), 8);
    }

    #[test]
    fn multiple_models() {
        let mut env = Env::new("").unwrap();
        env.set(param::OutputFlag, 0).unwrap();
        assert_eq!(env.get(param::UpdateMode).unwrap(), 1);

        let mut model1 = Model::with_env("model1", &env).unwrap();
        let mut model2 = Model::with_env("model1", &env).unwrap();

        let x1 = add_binvar!(model1, name:"x1").unwrap();
        let y1 = add_binvar!(model1, name:"y1").unwrap();
        let x2 = add_binvar!(model2, name:"x2").unwrap();
        let y2 = add_binvar!(model2, name:"y2").unwrap();

        model1.add_constr("", c!(x1 <= y1)).unwrap();
        model2.add_constr("", c!(x2 <= y2)).unwrap();
        assert_eq!(
            model1.add_constr("", c!(x2 <= y1)).unwrap_err(),
            Error::ModelObjectMismatch
        );
        assert_eq!(
            model1.add_constr("", c!(x1 <= y2)).unwrap_err(),
            Error::ModelObjectMismatch
        );

        model1.update().unwrap();
        model2.update().unwrap();

        assert_eq!(
            model1.get_obj_attr(attr::VarName, &x1).unwrap(),
            "x1".to_string()
        );
        assert_eq!(
            model1.get_obj_attr(attr::VarName, &y1).unwrap(),
            "y1".to_string()
        );
        assert_eq!(
            model2.get_obj_attr(attr::VarName, &x2).unwrap(),
            "x2".to_string()
        );
        assert_eq!(
            model2.get_obj_attr(attr::VarName, &y2).unwrap(),
            "y2".to_string()
        );

        assert_eq!(
            model1.get_obj_attr(attr::VarName, &y2).unwrap_err(),
            Error::ModelObjectMismatch
        );
        assert_eq!(
            model2.get_obj_attr(attr::VarName, &x1).unwrap_err(),
            Error::ModelObjectMismatch
        );
    }

    #[test]
    fn new_model_copies_env() -> Result<()> {
        let mut env = Env::new("")?;
        env.set(param::OutputFlag, 0)?;
        let mut model = Model::with_env("test", &env)?;
        let model_env = model.get_env_mut();
        // assert_eq!(model.get)

        model_env.set(param::OutputFlag, 1)?;
        assert_eq!(model_env.get(param::OutputFlag), Ok(1));
        assert_eq!(env.get(param::OutputFlag), Ok(0));

        assert_ne!(model_env.as_ptr(), env.as_ptr());
        Ok(())
    }

    #[test]
    fn new_model_copies_env_drop() -> Result<()> {
        let mut env = Env::new("")?;
        env.set(param::OutputFlag, 0)?;
        let mut model = Model::with_env("test", &env)?;
        drop(env); // frees underlying GRBEnv
        let model_env = model.get_env_mut();
        model_env.set(param::OutputFlag, 1)?;
        assert_eq!(model_env.get(param::OutputFlag), Ok(1));
        Ok(())
    }

    #[test]
    fn model_copy_copies_env() -> Result<()> {
        let mut env = Env::new("")?;
        env.set(param::OutputFlag, 0)?;
        let mut m1 = Model::with_env("m1", &env)?;
        let m2 = m1.try_clone()?;

        let m1_env = m1.get_env_mut();
        let m2_env = m2.get_env();

        m1_env.set(param::OutputFlag, 1)?;

        assert_eq!(m1_env.get(param::OutputFlag), Ok(1));
        assert_eq!(m2_env.get(param::OutputFlag), Ok(0));
        Ok(())
    }

    #[test]
    fn fixed_mip_model_copies_env() -> Result<()> {
        let mut m = Model::new("")?;
        m.set_param(param::OutputFlag, 0)?;
        let x = add_var!(m, Continuous, name:"x")?;
        let y = add_binvar!(m, name:"y")?;

        m.add_constr("c1", c!(x + y <= 1))?;
        m.add_constr("c1", c!(x - y <= 2))?;

        m.optimize()?;
        let fixed = m.fixed()?;
        assert_eq!(fixed.get_attr(attr::IsMIP)?, 0);
        assert_eq!(fixed.get_env().get(param::OutputFlag)?, 0);

        m.get_env_mut().set(param::OutputFlag, 1)?;
        assert_eq!(fixed.get_env().get(param::OutputFlag)?, 0);

        assert_ne!(m.get_env().as_ptr(), fixed.get_env().as_ptr());

        Ok(())
    }

    #[test]
    fn read_model_copies_env() -> Result<()> {
        let env = Env::new("")?;
        let m1 = Model::with_env("test", &env)?;
        let filename = "test_read_model_copies_env.lp";
        m1.write(filename)?;
        let m2 = Model::read_from(filename, &env)?;
        assert_ne!(m2.get_env().as_ptr(), m1.get_env().as_ptr());
        Ok(())
    }

    #[test]
    fn copy_env_model_to_model() -> Result<()> {
        let env = Env::new("")?;
        let m1 = Model::with_env("", &env)?;
        let m2 = Model::with_env("", m1.get_env())?;

        assert_ne!(m1.get_env().as_ptr(), m2.get_env().as_ptr());
        Ok(())
    }

    #[test]
    fn objective_constant() -> Result<()> {
        let mut m = Model::new("")?;
        let x = add_ctsvar!(m)?;
        m.set_objective(x + 1, Minimize)?; // x has ub of 0
        m.optimize()?;
        assert_eq!(m.get_attr(attr::ObjVal)?.round() as usize, 1); // obj = x^* + 1 = 0 + 1
        Ok(())
    }
}