Skip to content

Commit

Permalink
fix: Updating parser such that objective names can be omitted (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
dandxy89 committed Jan 3, 2024
1 parent 5a58e64 commit ab2c7da
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 7 deletions.
66 changes: 66 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 @@ -15,6 +15,7 @@ anyhow = "1.0"
pest = "2.7"
pest_derive = "2.7"
serde = { version = "1", features = ["derive"], optional = true }
tiny_id = "0.1.6"

[features]
default = []
Expand Down
11 changes: 11 additions & 0 deletions resources/optional_labels.lp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
\ LP format example

Minimize
x + 10 y
Subject To
r01: x + y >= 1

Binaries
x y z a
End

2 changes: 1 addition & 1 deletion src/lp_file_format.pest
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// https://www.ibm.com/docs/en/icos/22.1.1?topic=cplex-lp-file-format-algebraic-representation
// https://www.fico.com/fico-xpress-optimization/docs/dms2020-03/solver/optimizer/HTML/chapter10_sec_section102.html
// https://www.gurobi.com/documentation/current/refman/lp_format.html
//
//

// Common
WHITESPACE = _{ " " }
Expand Down
17 changes: 12 additions & 5 deletions src/lp_parts.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::str::FromStr;

use pest::iterators::Pair;
use tiny_id::ShortCodeGenerator;

use crate::{
common::RuleExt,
Expand All @@ -9,9 +10,14 @@ use crate::{
};

#[allow(clippy::unwrap_used)]
fn compose_objective(pair: Pair<'_, Rule>) -> anyhow::Result<Objective> {
let mut parts = pair.into_inner();
let name = parts.next().unwrap().as_str().to_string();
fn compose_objective(pair: Pair<'_, Rule>, gen: &mut ShortCodeGenerator<char>) -> anyhow::Result<Objective> {
let mut parts = pair.into_inner().peekable();
// Objective name can be omitted in LP files, so we need to handle that case
let name = if parts.peek().unwrap().as_rule() == Rule::OBJECTIVE_NAME {
parts.next().unwrap().as_str().to_string()
} else {
format!("obj_{}", gen.next_string())
};
let coefficients: anyhow::Result<Vec<_>> = parts.map(|p| p.into_inner().try_into()).collect();
Ok(Objective { name, coefficients: coefficients? })
}
Expand Down Expand Up @@ -84,7 +90,7 @@ fn get_bound(pair: Pair<'_, Rule>) -> Option<(&str, VariableType)> {
#[allow(clippy::wildcard_enum_match_arm)]
/// # Errors
/// Returns an error if the `compose` fails
pub fn compose(pair: Pair<'_, Rule>, mut parsed: LPProblem) -> anyhow::Result<LPProblem> {
pub fn compose(pair: Pair<'_, Rule>, mut parsed: LPProblem, gen: &mut ShortCodeGenerator<char>) -> anyhow::Result<LPProblem> {
match pair.as_rule() {
// Problem Name
Rule::PROBLEM_NAME => return Ok(parsed.with_problem_name(pair.as_str())),
Expand All @@ -93,7 +99,8 @@ pub fn compose(pair: Pair<'_, Rule>, mut parsed: LPProblem) -> anyhow::Result<LP
Rule::MAX_SENSE => return Ok(parsed.with_sense(Sense::Maximize)),
// Problem Objectives
Rule::OBJECTIVES => {
let objectives: anyhow::Result<Vec<Objective>> = pair.into_inner().map(|inner_pair| compose_objective(inner_pair)).collect();
let objectives: anyhow::Result<Vec<Objective>> =
pair.into_inner().map(|inner_pair| compose_objective(inner_pair, gen)).collect();
parsed.add_objective(objectives?);
}
// Problem Constraints
Expand Down
4 changes: 3 additions & 1 deletion src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::{

use crate::{lp_parts::compose, model::lp_problem::LPProblem, LParser, Rule};
use pest::Parser;
use tiny_id::ShortCodeGenerator;

/// # Errors
/// Returns an error if the `read_to_string` or `open` fails
Expand All @@ -28,8 +29,9 @@ pub fn parse_lp_file(contents: &str) -> anyhow::Result<LPProblem> {
anyhow::bail!("Invalid LP file");
};
let mut parsed_contents = LPProblem::default();
let mut code_generator = ShortCodeGenerator::new_lowercase_alphanumeric(6);
for pair in pair.clone().into_inner() {
parsed_contents = compose(pair, parsed_contents)?;
parsed_contents = compose(pair, parsed_contents, &mut code_generator)?;
}
Ok(parsed_contents)
}
1 change: 1 addition & 0 deletions tests/test_from_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ generate_test!(test, "test.lp", Maximize, 1, 4, 12);
generate_test!(test2, "test2.lp", Maximize, 1, 7, 139);
generate_test!(empty_bounds, "empty_bounds.lp", Minimize, 1, 1, 2);
generate_test!(blank_lines, "blank_lines.lp", Minimize, 1, 1, 3);
generate_test!(optional_labels, "optional_labels.lp", Minimize, 1, 1, 4);

#[test]
#[ignore = "fit2d.mps takes > 60 seconds"]
Expand Down

0 comments on commit ab2c7da

Please sign in to comment.