Skip to main content

highs/
matrix_row.rs

1//! row-oriented matrix to build a problem constraint by constraint
2use std::borrow::Borrow;
3use std::convert::TryInto;
4use std::ops::RangeBounds;
5use std::os::raw::c_int;
6
7use crate::matrix_col::ColMatrix;
8use crate::{Integrality, Problem};
9
10/// Represents a variable
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub struct Col(pub(crate) usize);
13
14impl Col {
15    /// Gets the index of the column
16    pub fn index(self) -> usize {
17        self.0
18    }
19}
20
21/// A complete optimization problem stored by row
22#[derive(Debug, Clone, PartialEq, Default)]
23pub struct RowMatrix {
24    /// column-wise sparse constraints  matrix
25    /// Each element in the outer vector represents a column (a variable)
26    columns: Vec<(Vec<c_int>, Vec<f64>)>,
27}
28
29/// Functions to use when first declaring variables, then constraints.
30impl Problem<RowMatrix> {
31    /// add a variable to the problem.
32    ///  - `col_factor` is the coefficient in front of the variable in the objective function.
33    ///  - `bounds` are the maximal and minimal values that the variable can take.
34    pub fn add_column<N: Into<f64> + Copy, B: RangeBounds<N>>(
35        &mut self,
36        col_factor: f64,
37        bounds: B,
38    ) -> Col {
39        self.add_column_with_integrality(col_factor, bounds, false)
40    }
41
42    /// Same as add_column, but forces the solution to contain an integer value for this variable.
43    pub fn add_integer_column<N: Into<f64> + Copy, B: RangeBounds<N>>(
44        &mut self,
45        col_factor: f64,
46        bounds: B,
47    ) -> Col {
48        self.add_column_with_integrality(col_factor, bounds, true)
49    }
50
51    /// Same as add_column, but lets you define whether the new variable should be integral or continuous.
52    #[inline]
53    pub fn add_column_with_integrality<N: Into<f64> + Copy, B: RangeBounds<N>>(
54        &mut self,
55        col_factor: f64,
56        bounds: B,
57        is_integer: bool,
58    ) -> Col {
59        self.add_column_with_integrality_kind(col_factor, bounds, is_integer.into())
60    }
61
62    /// Same as add_column, but lets you set the variable's [`Integrality`]
63    /// (continuous, integer, semicontinuous, or semi-integer).
64    #[inline]
65    pub fn add_column_with_integrality_kind<N: Into<f64> + Copy, B: RangeBounds<N>>(
66        &mut self,
67        col_factor: f64,
68        bounds: B,
69        integrality: Integrality,
70    ) -> Col {
71        let col = Col(self.num_cols());
72        self.add_column_inner(col_factor, bounds, integrality);
73        self.matrix.columns.push((vec![], vec![]));
74        col
75    }
76
77    /// Add a semicontinuous variable: its value is `0` or within `bounds`.
78    ///
79    /// The lower bound is the threshold below which (other than `0`) the
80    /// variable may not lie. A finite upper bound is recommended by HiGHS,
81    /// though an unbounded upper bound is also accepted.
82    pub fn add_semi_continuous_column<N: Into<f64> + Copy, B: RangeBounds<N>>(
83        &mut self,
84        col_factor: f64,
85        bounds: B,
86    ) -> Col {
87        self.add_column_with_integrality_kind(col_factor, bounds, Integrality::SemiContinuous)
88    }
89
90    /// Add a semi-integer variable: its value is `0` or an integer within `bounds`.
91    ///
92    /// The lower bound is the threshold below which (other than `0`) the
93    /// variable may not lie. A finite upper bound is recommended by HiGHS,
94    /// though an unbounded upper bound is also accepted.
95    pub fn add_semi_integer_column<N: Into<f64> + Copy, B: RangeBounds<N>>(
96        &mut self,
97        col_factor: f64,
98        bounds: B,
99    ) -> Col {
100        self.add_column_with_integrality_kind(col_factor, bounds, Integrality::SemiInteger)
101    }
102
103    /// Add a constraint to the problem.
104    ///  - `bounds` are the maximal and minimal allowed values for the linear expression in the constraint
105    ///  - `row_factors` are the coefficients in the linear expression expressing the constraint
106    ///
107    /// ```
108    /// use highs::*;
109    /// let mut pb = RowProblem::new();
110    /// // Optimize 3x - 2y with x<=6 and y>=5
111    /// let x = pb.add_column(3., ..6);
112    /// let y = pb.add_column(-2., 5..);
113    /// pb.add_row(2.., &[(x, 3.), (y, 8.)]); // 2 <= x*3 + y*8
114    /// ```
115    pub fn add_row<
116        N: Into<f64> + Copy,
117        B: RangeBounds<N>,
118        ITEM: Borrow<(Col, f64)>,
119        I: IntoIterator<Item = ITEM>,
120    >(
121        &mut self,
122        bounds: B,
123        row_factors: I,
124    ) {
125        let num_rows: c_int = self.num_rows().try_into().expect("too many rows");
126        for r in row_factors {
127            let &(col, factor) = r.borrow();
128            let c = &mut self.matrix.columns[col.0];
129            c.0.push(num_rows);
130            c.1.push(factor);
131        }
132        self.add_row_inner(bounds);
133    }
134}
135
136impl From<RowMatrix> for ColMatrix {
137    fn from(m: RowMatrix) -> Self {
138        let mut astart = Vec::with_capacity(m.columns.len());
139        astart.push(0);
140        let size: usize = m.columns.iter().map(|(v, _)| v.len()).sum();
141        let mut aindex = Vec::with_capacity(size);
142        let mut avalue = Vec::with_capacity(size);
143        for (row_indices, factors) in m.columns {
144            aindex.extend_from_slice(&row_indices);
145            avalue.extend_from_slice(&factors);
146            astart.push(aindex.len().try_into().expect("invalid matrix size"));
147        }
148        Self {
149            astart,
150            aindex,
151            avalue,
152        }
153    }
154}
155
156#[allow(clippy::float_cmp)]
157#[test]
158fn test_conversion() {
159    use crate::status::HighsModelStatus::Optimal;
160    use crate::{ColProblem, Model, RowProblem, Sense};
161    let inf = f64::INFINITY;
162    let neg_inf = f64::NEG_INFINITY;
163    let mut p = RowProblem::default();
164    let x: Col = p.add_column(1., -1..2);
165    let y: Col = p.add_column(9., 4f64..inf);
166    p.add_row(-999f64..inf, [(x, 666.), (y, 777.)]);
167    p.add_row(neg_inf..8880f64, [(y, 888.)]);
168    assert_eq!(
169        p,
170        RowProblem {
171            colcost: vec![1., 9.],
172            collower: vec![-1., 4.],
173            colupper: vec![2., inf],
174            rowlower: vec![-999., neg_inf],
175            rowupper: vec![inf, 8880.],
176            integrality: None,
177            matrix: RowMatrix {
178                columns: vec![(vec![0], vec![666.]), (vec![0, 1], vec![777., 888.])],
179            },
180        }
181    );
182    let colpb = ColProblem::from(p.clone());
183    assert_eq!(
184        colpb,
185        ColProblem {
186            colcost: vec![1., 9.],
187            collower: vec![-1., 4.],
188            colupper: vec![2., inf],
189            rowlower: vec![-999., neg_inf],
190            rowupper: vec![inf, 8880.],
191            integrality: None,
192            matrix: ColMatrix {
193                astart: vec![0, 1, 3],
194                aindex: vec![0, 0, 1],
195                avalue: vec![666., 777., 888.],
196            },
197        }
198    );
199    let mut m = Model::new(p);
200    m.make_quiet();
201    m.set_sense(Sense::Maximise);
202    let solved = m.solve();
203    assert_eq!(solved.status(), Optimal);
204    assert_eq!(solved.get_solution().columns(), &[2., 10.]);
205}
206
207impl From<Problem<RowMatrix>> for Problem<ColMatrix> {
208    fn from(pb: Problem<RowMatrix>) -> Problem<ColMatrix> {
209        Self {
210            colcost: pb.colcost,
211            collower: pb.collower,
212            colupper: pb.colupper,
213            rowlower: pb.rowlower,
214            rowupper: pb.rowupper,
215            integrality: pb.integrality,
216            matrix: pb.matrix.into(),
217        }
218    }
219}