-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
268 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,260 @@ | ||
use nom::{ | ||
branch::alt, | ||
bytes::complete::{tag, tag_no_case, take_while1}, | ||
character::complete::{char, multispace0}, | ||
combinator::{map, opt, recognize, value}, | ||
multi::many1, | ||
number::complete::double, | ||
sequence::{delimited, pair, preceded, terminated, tuple}, | ||
IResult, | ||
}; | ||
|
||
const VALID_LP_CHARS: &[char] = &['!', '#', '$', '%', '&', '(', ')', '_', ',', '.', ';', '?', '@', '\\', '{', '}', '~', '\'']; | ||
|
||
#[derive(Debug, Clone, PartialEq)] | ||
pub struct Coefficient { | ||
pub var_name: String, | ||
pub coefficient: f64, | ||
} | ||
|
||
#[derive(Debug, Clone, PartialEq)] | ||
pub struct Objective { | ||
pub name: Option<String>, | ||
pub coefficients: Vec<Coefficient>, | ||
} | ||
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
pub enum Sense { | ||
Minimize, | ||
Maximize, | ||
} | ||
|
||
pub fn valid_lp_char(c: char) -> bool { | ||
c.is_alphanumeric() || VALID_LP_CHARS.contains(&c) | ||
} | ||
|
||
pub fn scientific_number(input: &str) -> IResult<&str, f64> { | ||
double(input) | ||
} | ||
|
||
#[inline] | ||
pub fn sign(input: &str) -> IResult<&str, f64> { | ||
alt((value(-1.0, char('-')), value(1.0, char('+')), value(1.0, multispace0)))(input) | ||
} | ||
|
||
#[inline] | ||
pub fn problem_name(input: &str) -> IResult<&str, &str> { | ||
recognize(take_while1(valid_lp_char))(input) | ||
} | ||
|
||
#[inline] | ||
pub fn problem_name_line(input: &str) -> IResult<&str, String> { | ||
map( | ||
delimited( | ||
tuple((multispace0, tag("\\"), multispace0, tag("Problem"), multispace0, tag("name:"), multispace0)), | ||
problem_name, | ||
multispace0, | ||
), | ||
String::from, | ||
)(input) | ||
} | ||
|
||
#[inline] | ||
pub fn minimize(input: &str) -> IResult<&str, Sense> { | ||
value(Sense::Minimize, alt((tag_no_case("minimize"), tag_no_case("minimum"), tag_no_case("min"))))(input) | ||
} | ||
|
||
#[inline] | ||
pub fn maximize(input: &str) -> IResult<&str, Sense> { | ||
value(Sense::Maximize, alt((tag_no_case("maximize"), tag_no_case("maximum"), tag_no_case("max"))))(input) | ||
} | ||
|
||
#[inline] | ||
pub fn problem_sense(input: &str) -> IResult<&str, Sense> { | ||
delimited(multispace0, alt((minimize, maximize)), multispace0)(input) | ||
} | ||
|
||
#[inline] | ||
pub fn problem_header(input: &str) -> IResult<&str, (Option<String>, Sense)> { | ||
pair(opt(problem_name_line), problem_sense)(input) | ||
} | ||
|
||
#[inline] | ||
pub fn variable(input: &str) -> IResult<&str, &str> { | ||
recognize(take_while1(valid_lp_char))(input) | ||
} | ||
|
||
#[inline] | ||
pub fn coefficient(input: &str) -> IResult<&str, Coefficient> { | ||
preceded( | ||
multispace0, | ||
map(tuple((sign, opt(terminated(scientific_number, multispace0)), variable)), |(sign, coef_opt, var_name)| Coefficient { | ||
var_name: var_name.to_string(), | ||
coefficient: sign * coef_opt.unwrap_or(1.0), | ||
}), | ||
)(input) | ||
} | ||
|
||
#[inline] | ||
pub fn objective_name(input: &str) -> IResult<&str, &str> { | ||
terminated(take_while1(valid_lp_char), delimited(multispace0, char(':'), multispace0))(input) | ||
} | ||
|
||
#[inline] | ||
pub fn term(input: &str) -> IResult<&str, Coefficient> { | ||
preceded( | ||
multispace0, | ||
alt(( | ||
map(preceded(char('-'), coefficient), |mut coef| { | ||
coef.coefficient = -coef.coefficient; | ||
coef | ||
}), | ||
preceded(opt(char('+')), coefficient), | ||
)), | ||
)(input) | ||
} | ||
|
||
#[inline] | ||
pub fn objective(input: &str) -> IResult<&str, Objective> { | ||
map(pair(opt(preceded(multispace0, objective_name)), many1(term)), |(name, coefficients)| Objective { | ||
name: name.map(String::from), | ||
coefficients, | ||
})(input) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use float_eq::assert_float_eq; | ||
|
||
use super::*; | ||
|
||
#[test] | ||
fn test_problem_name() { | ||
// Basic case | ||
assert_eq!(problem_name("MyProblem123"), Ok(("", "MyProblem123"))); | ||
// With special characters | ||
assert_eq!(problem_name("Test_Problem!#$"), Ok(("", "Test_Problem!#$"))); | ||
// With trailing content | ||
assert_eq!(problem_name("Problem1 rest"), Ok((" rest", "Problem1"))); | ||
} | ||
|
||
#[test] | ||
fn test_problem_name_line() { | ||
// Standard case | ||
assert_eq!(problem_name_line("\\ Problem name: TestProblem"), Ok(("", "TestProblem".to_string()))); | ||
// With extra whitespace | ||
assert_eq!(problem_name_line("\\ Problem name: SpacedProblem"), Ok(("", "SpacedProblem".to_string()))); | ||
// With special characters | ||
assert_eq!(problem_name_line("\\ Problem name: Test_Problem!#$"), Ok(("", "Test_Problem!#$".to_string()))); | ||
// With leading whitespace | ||
assert_eq!(problem_name_line(" \\ Problem name: LeadingSpace"), Ok(("", "LeadingSpace".to_string()))); | ||
} | ||
|
||
#[test] | ||
fn test_minimize() { | ||
assert_eq!(minimize("minimize"), Ok(("", Sense::Minimize))); | ||
assert_eq!(minimize("MINIMIZE"), Ok(("", Sense::Minimize))); | ||
assert_eq!(minimize("min"), Ok(("", Sense::Minimize))); | ||
assert_eq!(minimize("MIN"), Ok(("", Sense::Minimize))); | ||
assert_eq!(minimize("minimum"), Ok(("", Sense::Minimize))); | ||
assert_eq!(minimize("MINIMUM"), Ok(("", Sense::Minimize))); | ||
} | ||
|
||
#[test] | ||
fn test_maximize() { | ||
assert_eq!(maximize("maximize"), Ok(("", Sense::Maximize))); | ||
assert_eq!(maximize("MAXIMIZE"), Ok(("", Sense::Maximize))); | ||
assert_eq!(maximize("max"), Ok(("", Sense::Maximize))); | ||
assert_eq!(maximize("MAX"), Ok(("", Sense::Maximize))); | ||
assert_eq!(maximize("maximum"), Ok(("", Sense::Maximize))); | ||
assert_eq!(maximize("MAXIMUM"), Ok(("", Sense::Maximize))); | ||
} | ||
|
||
#[test] | ||
fn test_problem_sense() { | ||
// With leading whitespace | ||
assert_eq!(problem_sense(" minimize"), Ok(("", Sense::Minimize))); | ||
assert_eq!(problem_sense("\nmaximize"), Ok(("", Sense::Maximize))); | ||
// With trailing content | ||
assert_eq!(problem_sense("minimize obj"), Ok(("obj", Sense::Minimize))); | ||
assert_eq!(problem_sense("maximize obj"), Ok(("obj", Sense::Maximize))); | ||
// Case insensitive | ||
assert_eq!(problem_sense("MINIMIZE"), Ok(("", Sense::Minimize))); | ||
assert_eq!(problem_sense("MAXIMIZE"), Ok(("", Sense::Maximize))); | ||
} | ||
|
||
#[test] | ||
fn test_problem_header() { | ||
assert_eq!(problem_header("minimize"), Ok(("", (None, Sense::Minimize)))); | ||
assert_eq!(problem_header("\\ Problem name: Test\nminimize"), Ok(("", (Some("Test".to_string()), Sense::Minimize)))); | ||
assert_eq!(problem_header(" \\ Problem name: Test \n maximize "), Ok(("", (Some("Test".to_string()), Sense::Maximize)))); | ||
} | ||
|
||
#[test] | ||
fn test_scientific_number() { | ||
assert_float_eq!(scientific_number("1e-03").unwrap().1, 0.001, abs <= 1e-10); | ||
assert_float_eq!(scientific_number("2.5e+02").unwrap().1, 250.0, abs <= 1e-10); | ||
assert_float_eq!(scientific_number("3.14").unwrap().1, 3.14, abs <= 1e-10); | ||
} | ||
|
||
#[test] | ||
fn test_coefficient() { | ||
// Basic cases | ||
let (_, coef1) = coefficient("x1").unwrap(); | ||
assert_eq!(coef1.var_name, "x1"); | ||
assert_float_eq!(coef1.coefficient, 1.0, abs <= 1e-10); | ||
|
||
let (_, coef2) = coefficient("-2.5x2").unwrap(); | ||
assert_eq!(coef2.var_name, "x2"); | ||
assert_float_eq!(coef2.coefficient, -2.5, abs <= 1e-10); | ||
|
||
// Scientific notation | ||
let (_, coef3) = coefficient("1e-03x3").unwrap(); | ||
assert_eq!(coef3.var_name, "x3"); | ||
assert_float_eq!(coef3.coefficient, 0.001, abs <= 1e-10); | ||
|
||
// With spaces | ||
let (_, coef4) = coefficient(" +3.5 x4").unwrap(); | ||
assert_eq!(coef4.var_name, "x4"); | ||
assert_float_eq!(coef4.coefficient, 3.5, abs <= 1e-10); | ||
|
||
// Just variable with sign | ||
let (_, coef5) = coefficient(" -x5").unwrap(); | ||
assert_eq!(coef5.var_name, "x5"); | ||
assert_float_eq!(coef5.coefficient, -1.0, abs <= 1e-10); | ||
|
||
// Spaced scientic notation | ||
let (_, coef6) = coefficient(" +2.5e+02 x6").unwrap(); | ||
assert_eq!(coef6.var_name, "x6"); | ||
assert_float_eq!(coef6.coefficient, 250.0, abs <= 1e-10); | ||
} | ||
|
||
#[test] | ||
fn test_objective() { | ||
// Basic objective | ||
let (rest, obj1) = objective("obj1: x1 + 2 x2").unwrap(); | ||
assert_eq!(obj1.name, Some("obj1".to_string())); | ||
assert_eq!(obj1.coefficients.len(), 2); | ||
assert_eq!(obj1.coefficients[0].var_name, "x1"); | ||
assert_float_eq!(obj1.coefficients[0].coefficient, 1.0, abs <= 1e-10); | ||
assert_eq!(obj1.coefficients[1].var_name, "x2"); | ||
assert_float_eq!(obj1.coefficients[1].coefficient, 2.0, abs <= 1e-10); | ||
assert_eq!(rest, ""); | ||
|
||
// Objective with scientific notation | ||
let (_, obj2) = objective("obj: 1e-03x1 + 2.5e+02x2 - 3.14x3").unwrap(); | ||
assert_eq!(obj2.name, Some("obj".to_string())); | ||
assert_eq!(obj2.coefficients.len(), 3); | ||
assert_float_eq!(obj2.coefficients[0].coefficient, 0.001, abs <= 1e-10); | ||
assert_float_eq!(obj2.coefficients[1].coefficient, 250.0, abs <= 1e-10); | ||
assert_float_eq!(obj2.coefficients[2].coefficient, -3.14, abs <= 1e-10); | ||
|
||
// Objective with spaces | ||
let (_, obj3) = objective("obj: x1 - 2 x2 + 1.5 x3").unwrap(); | ||
assert_eq!(obj3.name, Some("obj".to_string())); | ||
assert_eq!(obj3.coefficients.len(), 3); | ||
assert_float_eq!(obj3.coefficients[0].coefficient, 1.0, abs <= 1e-10); | ||
assert_float_eq!(obj3.coefficients[1].coefficient, -2.0, abs <= 1e-10); | ||
assert_float_eq!(obj3.coefficients[2].coefficient, 1.5, abs <= 1e-10); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
|