use std::collections::HashSet;
use num_traits::Zero;
use relp_num::{NonZero, Signed};
use crate::algorithm::two_phase::matrix_provider::column::Column;
use crate::algorithm::two_phase::matrix_provider::column::identity::Identity;
use crate::algorithm::two_phase::matrix_provider::MatrixProvider;
use crate::algorithm::two_phase::strategy::pivot_rule::{PivotRule, SteepestDescentAlongObjective};
use crate::algorithm::two_phase::tableau::{debug_assert_in_basic_feasible_solution_state, Tableau};
use crate::algorithm::two_phase::tableau::inverse_maintenance::{ColumnComputationInfo, InverseMaintainer, ops as im_ops};
use crate::algorithm::two_phase::tableau::kind::artificial::Artificial;
use crate::algorithm::two_phase::tableau::kind::artificial::Cost;
use crate::algorithm::two_phase::tableau::kind::artificial::fully::Fully as FullyArtificial;
use crate::algorithm::two_phase::tableau::kind::artificial::partially::Partially as PartiallyArtificial;
pub trait FeasibilityComputeTrait: MatrixProvider<Column: Identity> {
fn compute_bfs_giving_im<IM>(&self) -> RankedFeasibilityResult<IM>
where
IM: InverseMaintainer<F:
im_ops::FieldHR +
im_ops::Column<<<Self as MatrixProvider>::Column as Column>::F> +
im_ops::Cost<Cost> +
im_ops::Rhs<<Self as MatrixProvider>::Rhs> +
>,
;
}
impl<MP> FeasibilityComputeTrait for MP
where
MP: MatrixProvider<Column: Identity>
{
default fn compute_bfs_giving_im<IM>(&self) -> RankedFeasibilityResult<IM>
where
IM: InverseMaintainer<F:
im_ops::FieldHR +
im_ops::Column<<<Self as MatrixProvider>::Column as Column>::F> +
im_ops::Cost<Cost> +
im_ops::Rhs<<Self as MatrixProvider>::Rhs> +
>,
{
let artificial_tableau = Tableau::<_, FullyArtificial<_>>::new(self);
primal::<_, _, MP, SteepestDescentAlongObjective<_>>(artificial_tableau)
}
}
pub trait PartialInitialBasis: MatrixProvider {
fn pivot_element_indices(&self) -> Vec<(usize, usize)>;
fn nr_initial_elements(&self) -> usize;
}
impl<MP: PartialInitialBasis> FeasibilityComputeTrait for MP
where
MP: MatrixProvider<Column: Identity>,
{
default fn compute_bfs_giving_im<IM>(&self) -> RankedFeasibilityResult<IM>
where
IM: InverseMaintainer<F:
im_ops::FieldHR +
im_ops::Column<<<Self as MatrixProvider>::Column as Column>::F> +
im_ops::Cost<Cost> +
im_ops::Rhs<<Self as MatrixProvider>::Rhs> +
>,
{
let artificial_tableau = Tableau::<_, PartiallyArtificial<_>>::new(self);
primal::<_, _, MP, SteepestDescentAlongObjective<_>>(artificial_tableau)
}
}
pub trait FullInitialBasis: PartialInitialBasis {
}
pub(crate) fn primal<IM, K, MP, PR>(
mut tableau: Tableau<IM, K>,
) -> RankedFeasibilityResult<IM>
where
IM: InverseMaintainer<F: im_ops::FieldHR + im_ops::Column<<K::Column as Column>::F> + im_ops::Cost<K::Cost>>,
K: Artificial,
MP: MatrixProvider,
PR: PivotRule<IM::F>,
{
let mut rule = PR::new(&tableau);
loop {
debug_assert!({
debug_assert_in_basic_feasible_solution_state(&tableau);
true
});
match rule.select_primal_pivot_column(&tableau) {
Some((pivot_column_index, cost)) => {
let column_with_info = tableau.generate_column(pivot_column_index);
match tableau.select_primal_pivot_row(column_with_info.column()) {
Some(pivot_row_index) => {
let basis_change_computation_info = tableau.bring_into_basis(
pivot_column_index, pivot_row_index,
column_with_info, cost,
);
rule.after_basis_update(basis_change_computation_info, &tableau);
}
None => panic!("Artificial cost can not be unbounded."),
}
}
None => break if tableau.objective_function_value().is_zero() {
let rank = if tableau.has_artificial_in_basis() {
let rows_to_remove = remove_artificial_basis_variables(&mut tableau);
if rows_to_remove.is_empty() {
Rank::Full
} else {
Rank::Deficient(rows_to_remove)
}
} else {
Rank::Full
};
let (im, nr_a, basis) = tableau.into_basis();
RankedFeasibilityResult::Feasible {
rank,
nr_artificial_variables: nr_a,
inverse_maintainer: im,
basis,
}
} else {
RankedFeasibilityResult::Infeasible
},
}
}
}
#[derive(Debug, Eq, PartialEq)]
pub enum RankedFeasibilityResult<IM> {
Feasible {
rank: Rank,
nr_artificial_variables: usize,
inverse_maintainer: IM,
basis: HashSet<usize>,
},
Infeasible,
}
#[derive(Debug, Eq, PartialEq)]
pub enum Rank {
Full,
Deficient(Vec<usize>),
}
fn remove_artificial_basis_variables<IM, K>(
tableau: &mut Tableau<IM, K>,
) -> Vec<usize>
where
IM: InverseMaintainer<F: im_ops::Column<<K::Column as Column>::F> + im_ops::Cost<K::Cost>>,
K: Artificial,
{
let artificial_variable_indices = tableau.artificial_basis_columns();
debug_assert!(artificial_variable_indices.is_sorted_by_key(|&(i, _)| i));
let mut rows_to_remove = Vec::new();
for (pivot_row, artificial) in artificial_variable_indices {
debug_assert!(tableau.is_in_basis(artificial));
let mut candidates = (tableau.nr_artificial_variables()..tableau.nr_columns())
.filter(|&j| !tableau.is_in_basis(j))
.map(|j| (j, tableau.relative_cost(j)));
let constraint_value = tableau.variable_value(artificial);
let column_and_cost = if constraint_value.is_not_zero() {
candidates
.filter(|(_, cost)| cost.is_zero())
.find(|&(j, _)| tableau.generate_element(pivot_row, j)
.map_or(false, |element| IM::F::is_positive(&element)))
} else {
candidates
.find(|&(j, _)| tableau.generate_element(pivot_row, j)
.map_or(false, |element| element.is_not_zero()))
};
if let Some((pivot_column, cost)) = column_and_cost {
let column = tableau.generate_column(pivot_column);
tableau.bring_into_basis(pivot_column, pivot_row, column, cost);
debug_assert!(!tableau.is_in_basis(artificial));
} else {
rows_to_remove.push(pivot_row);
}
debug_assert_in_basic_feasible_solution_state(tableau);
}
debug_assert!(rows_to_remove.is_sorted());
rows_to_remove
}
#[cfg(test)]
mod test {
use relp_num::{Rational64, RationalBig};
use crate::algorithm::two_phase::matrix_provider::matrix_data::MatrixData;
use crate::algorithm::two_phase::phase_one::{primal, Rank, RankedFeasibilityResult};
use crate::algorithm::two_phase::strategy::pivot_rule::FirstProfitable;
use crate::algorithm::two_phase::tableau::inverse_maintenance::carry::basis_inverse_rows::BasisInverseRows;
use crate::algorithm::two_phase::tableau::inverse_maintenance::carry::Carry;
use crate::algorithm::two_phase::tableau::kind::artificial::partially::Partially;
use crate::algorithm::two_phase::tableau::Tableau;
use crate::tests::problem_2::{create_matrix_data_data, matrix_data_form};
#[test]
fn finding_bfs() {
type T = Rational64;
type S = RationalBig;
let (constraints, b, variables) = create_matrix_data_data();
let matrix_data_form = matrix_data_form(&constraints, &b, &variables);
let tableau = Tableau::<Carry<S, BasisInverseRows<S>>, Partially<_>>::new(&matrix_data_form);
assert!(matches!(
primal::<_, _, MatrixData<T>, FirstProfitable>(tableau),
RankedFeasibilityResult::Feasible { rank: Rank::Full, .. }
));
}
}