Skip to content

Commit

Permalink
test: Adding more Tests to verify .pest spec
Browse files Browse the repository at this point in the history
  • Loading branch information
dandxy89 committed Nov 11, 2023
1 parent 927ac28 commit 9a50f09
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 19 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,5 @@ A Rust LP file parser leveraging [PEST](https://docs.rs/pest/latest/pest/) and a
Test data has been taken from another similar project:

- [asbestian/jplex](https://github.com/asbestian/jplex/blob/main/instances/afiro.lp)
- [odow/LPWriter.jl](https://github.com/odow/LPWriter.jl/blob/master/test/model2.lp)
- [aphi/Lp-Parser](https://github.com/aphi/Lp-Parser)
14 changes: 14 additions & 0 deletions resources/1obj_1cons_all_variables_with_bounds.lp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
\ Comment line

Max
cost: -2 x - 3y +4z
Subject To
cons: 4.4 x + 5.5y + 6.6z <= 120
Bounds
x free
10 <= y <= 12
Binary
z
General
y
End
13 changes: 13 additions & 0 deletions resources/2obj_2cons_all_variable_types.lp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
\ Comment line

Minimize
obj1: -0.5 x - 2y - 8z
obj2: y + x + z
Subject To
cons1: -199x + 10 y + z <= 0
cons2: y + z >= 0
Binary
x
General
y
End
11 changes: 11 additions & 0 deletions resources/2obj_2cons_only_binary_vars.lp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
\ Comment line

Minimize
obj1: -0.5 x - 2y - 8z
obj2: y + x + z
Subject To
cons1: -199x + 10 y + z <= 0
cons2: y + z >= 0
Binary
x y z
End
11 changes: 11 additions & 0 deletions resources/3obj_2cons.lp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
\Three objectives; 2 constraints.

Minimize
obj1: -0.5 x - 2y - 8z
obj2: y + x + z
obj3: 10z - 2.5x
+ y
Subject To
cons1: -199x + 10 y + z <= 0
cons2: y + z >= 0
End
7 changes: 7 additions & 0 deletions resources/invalid.lp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
max
obj: 1 x
Subject To
Bounds
x free
End
C: 1 x <= 2
16 changes: 16 additions & 0 deletions resources/limbo.lp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Minimize
b_5829890_x2
+ b_5880854_x2
Subject To
- 2 b_5829890_x2 + 2 b_5829890_x1 <= -64
b_5880854_x2 - b_5880854_x1 >= 32
Bounds
b_5829890_x1 >= 10
10 >= b_5880854_x1
1014 <= b_5829890_x2 <= 1917
1014 <= b_5880854_x2 <= 1917
Generals
b_5829890_x2 b_5880854_x2
Binary
b_5829890_x1
End
21 changes: 21 additions & 0 deletions resources/model2.lp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Minimize
obj: -1 V4 + 1 V5
Subject To
CON1: 1 V1 >= 0.0
CON2: 1 V2 >= 2.0
CON3: 1 V3 <= 2.5
CON4: 1 V5 + 1 V6 + 1 V7 <= 1.0
Bounds
-inf <= V1 <= 3
-inf <= V2 <= 3
-inf <= V3 <= 3
5.5 <= V4 <= +inf
0 <= V5 <= 1
0 <= V6 <= 1
0 <= V7 <= 1
0 <= V8 <= 1
General
V4
Binary
V8
End
8 changes: 8 additions & 0 deletions resources/no_end_section.lp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Minimize
obj1: -0.5 x - 2y - 8z
obj2: y + x + z
obj3: 10z - 2.5x
+ y
Subject To
cons1: -199x + 10 y + z <= 0
cons2: y + z >= 0
23 changes: 23 additions & 0 deletions resources/sos.lp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Maximize
obj: -1 V4 + 1 V5
Subject To
CON1: 1 V1 >= 0.0
CON2: 1 V2 >= 2.0
CON3: 1 V3 <= 2.5
CON4: 1 V5 + 1 V6 + 1 V7 <= 1.0
csos1: S1:: V1:1 V3:2 V5:3
csos2: S2:: V2:2 V4:1 V5:2.5
Bounds
-inf <= V1 <= 3
-inf <= V2 <= 3
-inf <= V3 <= 3
5.5 <= V4 <= +inf
0 <= V5 <= 1
0 <= V6 <= 1
0 <= V7 <= 1
0 <= V8 <= 1
General
V4
Binary
V8
End
30 changes: 16 additions & 14 deletions src/lp_file_format.pest
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ OBJECTIVE_EXPR = {
| NEWLINE ~ OPERATOR ~ FLOAT ~ VARIABLE
}
OBJECTIVE_NAME = { VALID_CHARS{1, 255} }
OBJECTIVE = { NEWLINE* ~ OBJECTIVE_NAME ~ COLON ~ OBJECTIVE_EXPR ~ (OBJECTIVE_EXPR)* }
OBJECTIVE = { NEWLINE* ~ (OBJECTIVE_NAME ~ COLON)? ~ OBJECTIVE_EXPR ~ (OBJECTIVE_EXPR)* }
OBJECTIVES = { OBJECTIVE* }

// Constraints
Expand All @@ -75,21 +75,22 @@ CMP = _{ GTE | GT | LTE | LT | EQ }
CONSTRAINT_EXPR = { OPERATOR? ~ FLOAT? ~ VARIABLE | OPERATOR? ~ FLOAT }
CONSTRAINT_NAME = { VALID_CHARS{1, 255} }
CONSTRAINT = {
NEWLINE? ~ CONSTRAINT_NAME ~ COLON? ~ CONSTRAINT_EXPR ~ (NEWLINE? ~ OPERATOR ~ CONSTRAINT_EXPR)* ~ NEWLINE? ~ CMP ~ FLOAT
NEWLINE? ~ (CONSTRAINT_NAME ~ COLON?)? ~ CONSTRAINT_EXPR ~ (NEWLINE? ~ OPERATOR ~ CONSTRAINT_EXPR)* ~ NEWLINE? ~ CMP ~ FLOAT
}
CONSTRAINTS = { NEWLINE* ~ CONSTRAINT_PREFIX ~ CONSTRAINT+ }

// Bounds
// https://www.ibm.com/docs/en/icos/22.1.1?topic=representation-bounds-in-lp-file-format
BOUND_PREFIX = _{ ^"bound" ~ ^"s"? }
BOUNDED = { FLOAT ~ LTE ~ VARIABLE ~ LTE ~ FLOAT }
FREE_VARIABLE = { VARIABLE ~ FREE }
LOWER_BOUND = { VARIABLE ~ GTE ~ FLOAT }
UPPER_BOUND = { VARIABLE ~ LTE ~ FLOAT }
BOUND = _{
NEWLINE ~ (FREE_VARIABLE | BOUNDED | UPPER_BOUND | LOWER_BOUND)
BOUND_PREFIX = _{ ^"bound" ~ ^"s"? }
BOUNDED = { FLOAT ~ LTE ~ VARIABLE ~ LTE ~ FLOAT }
FREE_VARIABLE = { VARIABLE ~ FREE }
LOWER_BOUND = { VARIABLE ~ GTE ~ FLOAT }
LOWER_BOUND_REV = { FLOAT ~ GTE ~ VARIABLE }
UPPER_BOUND = { VARIABLE ~ LTE ~ FLOAT }
BOUND = _{
NEWLINE ~ (FREE_VARIABLE | BOUNDED | UPPER_BOUND | LOWER_BOUND | LOWER_BOUND_REV)
}
BOUNDS = { NEWLINE* ~ BOUND_PREFIX ~ BOUND+ }
BOUNDS = { NEWLINE* ~ BOUND_PREFIX ~ BOUND+ }

// Integers
// A list of variable names of integer variables. Unless otherwise specified in the
Expand All @@ -100,19 +101,20 @@ INTEGERS = { NEWLINE ~ INTEGER_PREFIX ~ (NEWLINE ~ VARIABLE)+ }
// Generals
// A list of variable names of integer variables. Unless otherwise specified in the
// bounds section, the default relaxation interval of the variables is [0, +Infinity].
GENERALS_PREFIX = _{ ^"Gen" ~ ^"erals"? }
GENERALS_PREFIX = _{ ^"Gen" ~ ^"eral"? ~ ^"s"? }
GENERALS = { NEWLINE ~ GENERALS_PREFIX ~ (NEWLINE ~ VARIABLE)+ }

// Binaries
// A list of variable names of binary variables.
BINARIES_PREFIX = _{ ^"Binaries" }
BINARIES_PREFIX = _{ ^"Binar" ~ (^"ies" | ^"y") }
BINARIES = { NEWLINE ~ BINARIES_PREFIX ~ (NEWLINE ~ VARIABLE)+ }

// End of file
// https://www.ibm.com/docs/en/icos/22.1.1?topic=representation-end-file-in-lp-file-format
EOF = _{ NEWLINE* ~ END }

// Global
LP_FILE = {
LP_PROBLEM_NAME? ~ COMMENTS* ~ PROBLEM_SENSE ~ OBJECTIVES ~ CONSTRAINTS ~ BOUNDS? ~ INTEGERS? ~ GENERALS? ~ BINARIES? ~ EOF
OPTIONAL_SECTIONS = _{ BOUNDS? ~ INTEGERS? ~ GENERALS? ~ BINARIES? ~ EOF? }
LP_FILE = {
LP_PROBLEM_NAME? ~ COMMENTS* ~ PROBLEM_SENSE ~ OBJECTIVES ~ CONSTRAINTS ~ OPTIONAL_SECTIONS
}
6 changes: 6 additions & 0 deletions src/lp_parts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ fn get_bound(pair: Pair<'_, Rule>) -> Option<(&str, VariableType)> {
let _ = parts.next();
Some((name, VariableType::LB(parts.next().unwrap().as_str().trim().parse().unwrap())))
}
Rule::LOWER_BOUND_REV => {
let mut parts = pair.into_inner();
let value = parts.next().unwrap().as_str().trim().parse().unwrap();
let _ = parts.next();
Some((parts.next().unwrap().as_str().trim(), VariableType::LB(value)))
}
Rule::UPPER_BOUND => {
let mut parts = pair.into_inner();
let name = parts.next().unwrap().as_str().trim();
Expand Down
90 changes: 85 additions & 5 deletions tests/test_from_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,15 @@ fn fit1d() {
assert_eq!(result.variables.len(), 2053);
}

// #[test]
// fn fit2d() {
// let result = read_file_from_resources("fit2d.lp").unwrap();
// assert_eq!(result.problem_sense, Sense::Minimize);
// }
#[test]
#[ignore = "fit2d.mps takes > 60 seconds"]
fn fit2d() {
let result = read_file_from_resources("fit2d.lp").unwrap();
assert_eq!(result.problem_sense, Sense::Minimize);
assert_eq!(result.objectives.len(), 1);
assert_eq!(result.constraints.len(), 25);
assert_eq!(result.variables.len(), 21001);
}

#[test]
fn kb2() {
Expand Down Expand Up @@ -101,6 +105,82 @@ fn sc50a() {
assert_eq!(result.variables.len(), 70);
}

#[test]
fn invalid() {
let result = read_file_from_resources("invalid.lp");
assert!(result.is_err());
}

#[test]
fn no_end_section() {
let result = read_file_from_resources("no_end_section.lp").unwrap();
assert_eq!("", result.problem_name);
assert_eq!(result.problem_sense, Sense::Minimize);
assert_eq!(result.objectives.len(), 4);
assert_eq!(result.constraints.len(), 2);
assert_eq!(result.variables.len(), 6);
}

#[test]
fn model2() {
let result = read_file_from_resources("model2.lp").unwrap();
assert_eq!("", result.problem_name);
assert_eq!(result.problem_sense, Sense::Minimize);
assert_eq!(result.objectives.len(), 1);
assert_eq!(result.constraints.len(), 4);
assert_eq!(result.variables.len(), 16);
}

#[test]
fn limbo() {
let result = read_file_from_resources("limbo.lp").unwrap();
assert_eq!("", result.problem_name);
assert_eq!(result.problem_sense, Sense::Minimize);
assert_eq!(result.objectives.len(), 2);
assert_eq!(result.constraints.len(), 2);
assert_eq!(result.variables.len(), 8);
}

#[test]
fn obj3_2cons() {
let result = read_file_from_resources("3obj_2cons.lp").unwrap();
assert_eq!("", result.problem_name);
assert_eq!(result.problem_sense, Sense::Minimize);
assert_eq!(result.objectives.len(), 4);
assert_eq!(result.constraints.len(), 2);
assert_eq!(result.variables.len(), 6);
}

#[test]
fn obj_2cons_only_binary_vars() {
let result = read_file_from_resources("2obj_2cons_only_binary_vars.lp").unwrap();
assert_eq!("", result.problem_name);
assert_eq!(result.problem_sense, Sense::Minimize);
assert_eq!(result.objectives.len(), 2);
assert_eq!(result.constraints.len(), 2);
assert_eq!(result.variables.len(), 7);
}

#[test]
fn obj_2cons_all_variable_types() {
let result = read_file_from_resources("2obj_2cons_all_variable_types.lp").unwrap();
assert_eq!("", result.problem_name);
assert_eq!(result.problem_sense, Sense::Minimize);
assert_eq!(result.objectives.len(), 2);
assert_eq!(result.constraints.len(), 2);
assert_eq!(result.variables.len(), 7);
}

#[test]
fn obj_1cons_all_variables_with_bounds() {
let result = read_file_from_resources("1obj_1cons_all_variables_with_bounds.lp").unwrap();
assert_eq!("", result.problem_name);
assert_eq!(result.problem_sense, Sense::Maximize);
assert_eq!(result.objectives.len(), 1);
assert_eq!(result.constraints.len(), 1);
assert_eq!(result.variables.len(), 6);
}

fn read_file_from_resources(file_name: &str) -> anyhow::Result<LPDefinition> {
let mut file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
file_path.push(format!("resources/{file_name}"));
Expand Down

0 comments on commit 9a50f09

Please sign in to comment.