From ab2c7daf53b591c353057e75d6cefa4d4c17988a Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 3 Jan 2024 11:43:57 +0000 Subject: [PATCH] fix: Updating parser such that objective names can be omitted (#31) --- Cargo.lock | 66 ++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + resources/optional_labels.lp | 11 ++++++ src/lp_file_format.pest | 2 +- src/lp_parts.rs | 17 +++++++--- src/parse.rs | 4 ++- tests/test_from_file.rs | 1 + 7 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 resources/optional_labels.lp diff --git a/Cargo.lock b/Cargo.lock index cdf8844..da6ac35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,6 +62,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "libc" version = "0.2.151" @@ -76,6 +87,7 @@ dependencies = [ "pest", "pest_derive", "serde", + "tiny_id", ] [[package]] @@ -135,6 +147,12 @@ dependencies = [ "sha2", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.74" @@ -153,6 +171,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", + "serde", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", + "serde", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "serde", +] + [[package]] name = "serde" version = "1.0.194" @@ -215,6 +263,18 @@ dependencies = [ "syn", ] +[[package]] +name = "tiny_id" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26f686a3694293e6039185ef4cf4e3ca5101a09172426ac8da84c3d60564fb93" +dependencies = [ + "getrandom", + "rand", + "rand_chacha", + "serde", +] + [[package]] name = "typenum" version = "1.17.0" @@ -238,3 +298,9 @@ name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/Cargo.toml b/Cargo.toml index 56e35b4..c26ed7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 = [] diff --git a/resources/optional_labels.lp b/resources/optional_labels.lp new file mode 100644 index 0000000..003c11c --- /dev/null +++ b/resources/optional_labels.lp @@ -0,0 +1,11 @@ +\ LP format example + +Minimize +x + 10 y +Subject To +r01: x + y >= 1 + +Binaries + x y z a +End + diff --git a/src/lp_file_format.pest b/src/lp_file_format.pest index 113f5be..9fad425 100644 --- a/src/lp_file_format.pest +++ b/src/lp_file_format.pest @@ -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 = _{ " " } diff --git a/src/lp_parts.rs b/src/lp_parts.rs index 0ab055b..93f2c9a 100644 --- a/src/lp_parts.rs +++ b/src/lp_parts.rs @@ -1,6 +1,7 @@ use std::str::FromStr; use pest::iterators::Pair; +use tiny_id::ShortCodeGenerator; use crate::{ common::RuleExt, @@ -9,9 +10,14 @@ use crate::{ }; #[allow(clippy::unwrap_used)] -fn compose_objective(pair: Pair<'_, Rule>) -> anyhow::Result { - let mut parts = pair.into_inner(); - let name = parts.next().unwrap().as_str().to_string(); +fn compose_objective(pair: Pair<'_, Rule>, gen: &mut ShortCodeGenerator) -> anyhow::Result { + 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> = parts.map(|p| p.into_inner().try_into()).collect(); Ok(Objective { name, coefficients: coefficients? }) } @@ -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 { +pub fn compose(pair: Pair<'_, Rule>, mut parsed: LPProblem, gen: &mut ShortCodeGenerator) -> anyhow::Result { match pair.as_rule() { // Problem Name Rule::PROBLEM_NAME => return Ok(parsed.with_problem_name(pair.as_str())), @@ -93,7 +99,8 @@ pub fn compose(pair: Pair<'_, Rule>, mut parsed: LPProblem) -> anyhow::Result return Ok(parsed.with_sense(Sense::Maximize)), // Problem Objectives Rule::OBJECTIVES => { - let objectives: anyhow::Result> = pair.into_inner().map(|inner_pair| compose_objective(inner_pair)).collect(); + let objectives: anyhow::Result> = + pair.into_inner().map(|inner_pair| compose_objective(inner_pair, gen)).collect(); parsed.add_objective(objectives?); } // Problem Constraints diff --git a/src/parse.rs b/src/parse.rs index d2d88d1..dbddd7b 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -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 @@ -28,8 +29,9 @@ pub fn parse_lp_file(contents: &str) -> anyhow::Result { 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) } diff --git a/tests/test_from_file.rs b/tests/test_from_file.rs index 3cf22ed..0fde8f8 100644 --- a/tests/test_from_file.rs +++ b/tests/test_from_file.rs @@ -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"]