Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ thiserror = { version = "2.0", default-features = false }

[dev-dependencies]
rstest = "0.25"
itertools = "0.14.0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
itertools = "0.14.0"
itertools = "0.14"

4 changes: 4 additions & 0 deletions src/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ impl Symbol {
pub fn kind(&self) -> SymbolKind {
self.1
}

pub fn id(&self) -> usize {
self.0
}
Comment on lines +33 to +36
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Symbol is an internal type, so you might instead just make it a struct with named fields - this is a non-breaking change. Though it sort of implies that id is mutable. That said, not a big deal. Whatever makes sense on this.

}

pub fn near_zero(value: f64) -> bool {
Expand Down
53 changes: 49 additions & 4 deletions src/solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -647,18 +647,24 @@ impl Solver {

/// Compute the entering variable for a pivot operation.
///
/// This method will return first symbol in the objective function which
/// This method will return a symbol in the objective function which
/// is non-dummy and has a coefficient less than zero. If no symbol meets
/// the criteria, it means the objective function is at a minimum, and an
/// invalid symbol is returned.
/// Could return an External symbol
fn get_entering_symbol(objective: &Row) -> Symbol {
let mut entering = Symbol::invalid();
let mut min_id = usize::MAX;

for (symbol, value) in &objective.cells {
if symbol.kind() != SymbolKind::Dummy && *value < 0.0 {
return *symbol;
if symbol.id() < min_id {
min_id = symbol.id();
entering = *symbol;
}
}
}
Symbol::invalid()
entering
Comment on lines +656 to +667
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this use min_by_key. E.g. something like:

Suggested change
let mut entering = Symbol::invalid();
let mut min_id = usize::MAX;
for (symbol, value) in &objective.cells {
if symbol.kind() != SymbolKind::Dummy && *value < 0.0 {
return *symbol;
if symbol.id() < min_id {
min_id = symbol.id();
entering = *symbol;
}
}
}
Symbol::invalid()
entering
objective.cells
.iter()
.filter(|(symbol, value)| symbol.kind() != SymbolKind::Dummy && *value < 0.0)
.min_by_key(|(symbol, _)| symbol.id())
.unwrap_or(Symbol::invalid())

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably yes, this is a dirty branch, I'm just exploring what works at this moment so I want to keep the changes minimal.

}

/// Compute the entering symbol for the dual optimize operation.
Expand Down Expand Up @@ -709,13 +715,17 @@ impl Solver {
fn get_leaving_row(&mut self, entering: Symbol) -> Option<(Symbol, Box<Row>)> {
let mut ratio = f64::INFINITY;
let mut found = None;
let mut min_id = usize::MAX;

for (symbol, row) in &self.rows {
if symbol.kind() != SymbolKind::External {
let temp = row.coefficient_for(entering);
if temp < 0.0 {
let temp_ratio = -row.constant / temp;
if temp_ratio < ratio {
// in case of duplicates, choose symbol with the smallest id to prevent cycling
if temp_ratio < ratio || (temp_ratio == ratio && symbol.id() < min_id) {
ratio = temp_ratio;
min_id = symbol.id();
found = Some(*symbol);
}
}
Expand Down Expand Up @@ -816,3 +826,38 @@ impl Solver {
.unwrap_or(0.0)
}
}

#[cfg(test)]
mod tests {
use itertools::Itertools;

use super::*;
use crate::WeightedRelation::EQ;

#[test]
fn regression_18() {
const N: usize = 90;

let mut solver = Solver::new();

let variable_count = N * 2 + 2;
let variables = core::iter::repeat_with(Variable::new)
.take(variable_count)
.collect_vec();
let segments: Vec<(&Variable, &Variable)> = variables.iter().skip(1).tuples().collect_vec();

let constraints = alloc::vec![1.0; N];

for ((&left_constraint, &left_segment), (&right_constraint, &right_segment)) in
constraints.iter().zip(segments.iter()).tuple_combinations()
{
solver
.add_constraint(
(right_constraint * (*left_segment.1 - *left_segment.0))
| EQ(Strength::MEDIUM.div_f64(10.0))
| (left_constraint * (*right_segment.1 - *right_segment.0)),
)
.unwrap();
}
}
}
Loading