Skip to content

Commit

Permalink
feat: Adding semi-continous
Browse files Browse the repository at this point in the history
  • Loading branch information
dandxy89 committed Nov 11, 2023
1 parent 663f295 commit 51eae33
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 28 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ A Rust LP file parser leveraging [PEST](https://docs.rs/pest/latest/pest/) and a
- Constraints
- Bounds
- Variable Types: Integer, Generals, Lower Bounded, Upper Bounded, Free & Upper and Lower Bounded
- Semi-continuous

## TODOs List

- Remaining LP format changes:
- Semi-continuous
- <https://www.ibm.com/docs/en/icos/22.1.1?topic=representation-mip-features-in-lp-file-format#File_formats_reference.uss_reffileformatscplex.LP_MIP__File_formats_reference.uss_reffileformatscplex.177272__title__1>
- <https://www.fico.com/fico-xpress-optimization/docs/dms2020-03/solver/optimizer/HTML/chapter10_sec_section102.html>
- Special ordered sets: SOS
- Keyword: `SOS`
- Example: `Sos101: 1.2 x1 + 1.3 x2 + 1.4 x4 = S1`
- Lazy Constraints
- Semi-Continuous
- See details in FICO® Xpress
- Extensions:
- Compares two LP files
- CLI
Expand Down
18 changes: 18 additions & 0 deletions resources/semi_continuous.lp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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
Semi-continuous
x1 x2 x3
End
13 changes: 10 additions & 3 deletions src/lp_file_format.pest
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,15 @@ PROBLEM_SENSE = _{ NEWLINE* ~ (MIN_SENSE | MAX_SENSE) }
// https://www.ibm.com/docs/en/icos/22.1.1?topic=representation-variable-names-in-lp-file-format
// alphanumeric (a-z, A-Z, 0-9) or one of these symbols: !"#$%&/,.;?@_`'{}()|~'
VALID_CHARS = _{
!(FREE | END | CONSTRAINT_PREFIX) ~ (ASCII_ALPHANUMERIC | "!" | "#" | "$" | "%" | "&" | "(" | ")" | "," | "." | ";" | "?" | "@" | "_" | "‘" | "’" | "{" | "}" | "~")
!WHITESPACE ~ (ASCII_ALPHANUMERIC | "!" | "#" | "$" | "%" | "&" | "(" | ")" | "," | "." | ";" | "?" | "@" | "_" | "‘" | "’" | "{" | "}" | "~")
}
CONSTRAINT_PREFIX = _{
(^"subject to" | ^"such that" | ^"S.T.") ~ COLON? ~ NEWLINE?
}
VARIABLE = {
!(FREE | END | CONSTRAINT_PREFIX | GENERALS_PREFIX | BINARIES_PREFIX) ~ VALID_CHARS{1, 255}
!(FREE | END | CONSTRAINT_PREFIX | GENERALS_PREFIX | BINARIES_PREFIX | SEMI_CONTINUOUS_PREFIX) ~ VALID_CHARS{1, 255}
}
SEMI_VARIABLE = { !(FREE | END | CONSTRAINT_PREFIX | GENERALS_PREFIX | BINARIES_PREFIX | SEMI_CONTINUOUS_PREFIX) ~ VALID_CHARS ~ (!WHITESPACE ~ VALID_CHARS) }

// Objective function
// https://www.ibm.com/docs/en/icos/22.1.1?topic=representation-objective-in-lp-file-format
Expand Down Expand Up @@ -109,12 +110,18 @@ GENERALS = { NEWLINE ~ GENERALS_PREFIX ~ (NEWLINE ~ VARIABLE)+ }
BINARIES_PREFIX = _{ ^"Binar" ~ (^"ies" | ^"y") }
BINARIES = { NEWLINE ~ BINARIES_PREFIX ~ (NEWLINE ~ VARIABLE)+ }

// Semi-Continuous
// To specify any of the variables as semi-continuous variables, that is as variables that
// may take the value 0 or values between the specified lower and upper bounds
SEMI_CONTINUOUS_PREFIX = _{ ^"SEMI" ~ (^"S" | ^"-CONTINUOUS") }
SEMI_CONTINUOUS = { NEWLINE ~ SEMI_CONTINUOUS_PREFIX ~ (NEWLINE? ~ SEMI_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
OPTIONAL_SECTIONS = _{ BOUNDS? ~ INTEGERS? ~ GENERALS? ~ BINARIES? ~ EOF? }
OPTIONAL_SECTIONS = _{ BOUNDS? ~ INTEGERS? ~ GENERALS? ~ BINARIES? ~ SEMI_CONTINUOUS? ~ EOF? }
LP_FILE = {
LP_PROBLEM_NAME? ~ COMMENTS* ~ PROBLEM_SENSE ~ OBJECTIVES ~ CONSTRAINTS ~ OPTIONAL_SECTIONS
}
8 changes: 8 additions & 0 deletions src/lp_parts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@ pub fn compose(pair: Pair<'_, Rule>, mut parsed: LPDefinition) -> anyhow::Result
}
}
}
// Problem Semi-continuous
Rule::SEMI_CONTINUOUS => {
for bin_pair in pair.into_inner() {
if matches!(bin_pair.as_rule(), Rule::SEMI_VARIABLE) {
parsed.set_var_bounds(bin_pair.as_str(), VariableType::SemiContinuous);
}
}
}
// Otherwise, skip!
_ => (),
}
Expand Down
16 changes: 11 additions & 5 deletions src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ pub enum VariableType {
#[default]
// General variable [0, +Infinity]
General,
// Semi-continuous
SemiContinuous,
}

#[derive(Debug)]
Expand Down Expand Up @@ -102,14 +104,18 @@ impl LPDefinition {
}

pub fn add_variable(&mut self, name: &str) {
self.variables.entry(name.to_string()).or_default();
if !name.is_empty() {
self.variables.entry(name.trim().to_string()).or_default();
}
}

pub fn set_var_bounds(&mut self, name: &str, kind: VariableType) {
match self.variables.entry(name.to_string()) {
Entry::Occupied(k) => *k.into_mut() = kind,
Entry::Vacant(k) => {
k.insert(kind);
if !name.is_empty() {
match self.variables.entry(name.trim().to_string()) {
Entry::Occupied(k) => *k.into_mut() = kind,
Entry::Vacant(k) => {
k.insert(kind);
}
}
}
}
Expand Down
44 changes: 27 additions & 17 deletions tests/test_from_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fn afiro() {
assert_eq!(result.problem_sense, Sense::Minimize);
assert_eq!(result.objectives.len(), 3);
assert_eq!(result.constraints.len(), 27);
assert_eq!(result.variables.len(), 44);
assert_eq!(result.variables.len(), 32);
}

#[test]
Expand All @@ -22,7 +22,7 @@ fn afiro_ext() {
assert_eq!(result.problem_sense, Sense::Minimize);
assert_eq!(result.objectives.len(), 4);
assert_eq!(result.constraints.len(), 27);
assert_eq!(result.variables.len(), 66);
assert_eq!(result.variables.len(), 47);
}

#[test]
Expand All @@ -32,7 +32,7 @@ fn boeing1() {
assert_eq!(result.problem_sense, Sense::Minimize);
assert_eq!(result.objectives.len(), 1);
assert_eq!(result.constraints.len(), 348);
assert_eq!(result.variables.len(), 856);
assert_eq!(result.variables.len(), 473);
}

#[test]
Expand All @@ -42,7 +42,7 @@ fn boeing2() {
assert_eq!(result.problem_sense, Sense::Minimize);
assert_eq!(result.objectives.len(), 1);
assert_eq!(result.constraints.len(), 140);
assert_eq!(result.variables.len(), 280);
assert_eq!(result.variables.len(), 162);
}

#[test]
Expand All @@ -52,7 +52,7 @@ fn fit1d() {
assert_eq!(result.problem_sense, Sense::Minimize);
assert_eq!(result.objectives.len(), 1);
assert_eq!(result.constraints.len(), 24);
assert_eq!(result.variables.len(), 2053);
assert_eq!(result.variables.len(), 1026);
}

#[test]
Expand All @@ -62,7 +62,7 @@ fn fit2d() {
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);
assert_eq!(result.variables.len(), 10500);
}

#[test]
Expand All @@ -72,7 +72,7 @@ fn kb2() {
assert_eq!(result.problem_sense, Sense::Minimize);
assert_eq!(result.objectives.len(), 1);
assert_eq!(result.constraints.len(), 43);
assert_eq!(result.variables.len(), 79);
assert_eq!(result.variables.len(), 41);
}

#[test]
Expand All @@ -82,7 +82,7 @@ fn pulp() {
assert_eq!(result.problem_sense, Sense::Minimize);
assert_eq!(result.objectives.len(), 1);
assert_eq!(result.constraints.len(), 49);
assert_eq!(result.variables.len(), 86);
assert_eq!(result.variables.len(), 62);
}

#[test]
Expand All @@ -92,7 +92,7 @@ fn pulp2() {
assert_eq!(result.problem_sense, Sense::Maximize);
assert_eq!(result.objectives.len(), 1);
assert_eq!(result.constraints.len(), 7);
assert_eq!(result.variables.len(), 148);
assert_eq!(result.variables.len(), 139);
}

#[test]
Expand All @@ -102,7 +102,7 @@ fn sc50a() {
assert_eq!(result.problem_sense, Sense::Minimize);
assert_eq!(result.objectives.len(), 1);
assert_eq!(result.constraints.len(), 49);
assert_eq!(result.variables.len(), 70);
assert_eq!(result.variables.len(), 48);
}

#[test]
Expand All @@ -118,7 +118,7 @@ fn no_end_section() {
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);
assert_eq!(result.variables.len(), 3);
}

#[test]
Expand All @@ -128,7 +128,7 @@ fn model2() {
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);
assert_eq!(result.variables.len(), 8);
}

#[test]
Expand All @@ -138,7 +138,7 @@ fn limbo() {
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);
assert_eq!(result.variables.len(), 5);
}

#[test]
Expand All @@ -148,7 +148,7 @@ fn obj3_2cons() {
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);
assert_eq!(result.variables.len(), 3);
}

#[test]
Expand All @@ -158,7 +158,7 @@ fn obj_2cons_only_binary_vars() {
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);
assert_eq!(result.variables.len(), 4);
}

#[test]
Expand All @@ -168,7 +168,7 @@ fn obj_2cons_all_variable_types() {
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);
assert_eq!(result.variables.len(), 3);
}

#[test]
Expand All @@ -178,7 +178,17 @@ fn obj_1cons_all_variables_with_bounds() {
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);
assert_eq!(result.variables.len(), 3);
}

#[test]
fn semi_continuous() {
let result = read_file_from_resources("semi_continuous.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!(dbg!(result.variables).len(), 8);
}

fn read_file_from_resources(file_name: &str) -> anyhow::Result<LPDefinition> {
Expand Down

0 comments on commit 51eae33

Please sign in to comment.