Skip to content

Commit

Permalink
feat: Adding implementation for Objectives (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
dandxy89 committed Nov 11, 2023
1 parent f36ce36 commit 9de11e1
Show file tree
Hide file tree
Showing 9 changed files with 348 additions and 1 deletion.
7 changes: 7 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
Expand Up @@ -7,6 +7,7 @@ rust-version = "1.70"
publish = false

[dependencies]
anyhow = "1.0.75"
pest = "2.7.5"
pest_derive = "2.7.5"

Expand Down
5 changes: 5 additions & 0 deletions rustfmt.toml
@@ -0,0 +1,5 @@
edition = "2021"
reorder_modules = false
use_small_heuristics = "Max"
max_width = 140
imports_granularity = "Crate"
32 changes: 32 additions & 0 deletions src/common.rs
@@ -0,0 +1,32 @@
use crate::Rule;
use pest::iterators::Pair;

pub trait IsNumeric {
fn is_numeric(&self) -> bool;
}

impl IsNumeric for Rule {
fn is_numeric(&self) -> bool {
matches!(self, Self::FLOAT | Self::PLUS | Self::MINUS | Self::POS_INFINITY | Self::NEG_INFINITY)
}
}

pub trait AsFloat {
/// # Errors
/// Returns an error if the rule cannot be converted to a float
fn as_float(&self) -> anyhow::Result<f64>;
}

impl AsFloat for Pair<'_, Rule> {
#[allow(clippy::unreachable, clippy::wildcard_enum_match_arm)]
fn as_float(&self) -> anyhow::Result<f64> {
match self.as_rule() {
Rule::POS_INFINITY => Ok(f64::INFINITY),
Rule::NEG_INFINITY => Ok(f64::NEG_INFINITY),
Rule::FLOAT => Ok(self.as_str().trim().parse()?),
Rule::PLUS => Ok(1.0),
Rule::MINUS => Ok(-1.0),
_ => unreachable!("Unexpected rule observed: {:?}", self.as_rule()),
}
}
}
4 changes: 4 additions & 0 deletions src/lib.rs
Expand Up @@ -30,6 +30,10 @@

use pest_derive::Parser;

pub mod common;
pub mod model;
pub mod parse;

#[derive(Parser)]
#[grammar = "lp_file_format.pest"]
pub struct LParser;
5 changes: 4 additions & 1 deletion src/lp_file_format.pest
@@ -1,6 +1,8 @@
// Spec:
// 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 All @@ -9,14 +11,15 @@ NEG_INFINITY = { "-" ~ ^"inf" ~ ^"inity"? }
FLOAT = { POS_INFINITY | NEG_INFINITY | ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT+)? }
PLUS = { "+" }
MINUS = { "-" }
OPERATOR = { PLUS | MINUS }
OPERATOR = _{ PLUS | MINUS }
COLON = _{ ":" }
ASTERIX = _{ "*" }
FREE = { ^"FREE" }
END = _{ ^"END" }

// Comments
// https://www.ibm.com/docs/en/icos/22.1.1?topic=representation-comments-in-lp-file-format
// TODO: Problem Name
COMMENT_TEXT = _{ (VALID_CHARS | COLON)* }
COMMENTS = _{ "\\" ~ ASTERIX? ~ COMMENT_TEXT ~ ASTERIX? ~ NEWLINE? }

Expand Down
97 changes: 97 additions & 0 deletions src/model.rs
@@ -0,0 +1,97 @@
use std::collections::HashMap;

use pest::iterators::Pairs;

use crate::{
common::{AsFloat, IsNumeric},
Rule,
};

#[derive(Debug, Default)]
pub enum VariableType {
#[default]
Unbounded,
Bounded(f64, f64),
Free,
Integer,
Binary,
}

#[derive(Debug)]
pub struct Objective {
pub name: String,
pub coefficients: Vec<Coefficient>,
}

#[derive(Debug)]
pub struct Coefficient {
pub name: String,
pub coefficient: f64,
}

impl TryFrom<Pairs<'_, Rule>> for Coefficient {
type Error = anyhow::Error;

#[allow(clippy::unreachable, clippy::wildcard_enum_match_arm)]
fn try_from(values: Pairs<'_, Rule>) -> anyhow::Result<Self> {
let (mut value, mut name) = (1.0, String::new());
for item in values {
match item.as_rule() {
r if r.is_numeric() => {
value *= item.as_float()?;
}
Rule::VARIABLE => {
name = item.as_str().to_string();
}
_ => unreachable!(),
}
}
Ok(Self { name, coefficient: value })
}
}

#[derive(Debug)]
pub struct Constraint {
pub name: String,
pub coefficients: Vec<Coefficient>,
pub sense: String,
pub rhs: f64,
}

#[derive(Debug, Default, PartialEq, Eq)]
pub enum Sense {
#[default]
Minimize,
Maximize,
}

#[derive(Debug, Default)]
pub struct LPDefinition {
pub problem_sense: Sense,
pub variables: HashMap<String, VariableType>,
pub objectives: Vec<Objective>,
pub constraints: Vec<Constraint>,
}

impl LPDefinition {
#[must_use]
pub fn with_sense(&mut self, problem_sense: Sense) -> Self {
Self { problem_sense, ..Default::default() }
}

pub fn add_variable(&mut self, name: String) {
self.variables.entry(name).or_default();
}

pub fn set_var_bounds(&mut self, name: String, kind: VariableType) {
self.variables.entry(name).and_modify(|bound_kind| *bound_kind = kind);
}

pub fn add_objective(&mut self, objectives: Vec<Objective>) {
self.objectives = objectives;
}

pub fn add_constraints(&mut self, constraints: Vec<Constraint>) {
self.constraints = constraints;
}
}
124 changes: 124 additions & 0 deletions src/parse.rs
@@ -0,0 +1,124 @@
use std::{
fs::File,
io::{BufReader, Read},
path::Path,
};

use crate::{
model::{Constraint, LPDefinition, Objective, Sense},
LParser, Rule,
};
use pest::{iterators::Pair, Parser};

/// # Errors
/// Returns an error if the `read_to_string` or `open` fails
pub fn parse_file(path: &Path) -> anyhow::Result<String> {
let Ok(file) = File::open(path) else {
anyhow::bail!("Could not open file at {path:?}");
};
let mut buf_reader = BufReader::new(file);
let mut contents = String::new();
buf_reader.read_to_string(&mut contents)?;

Ok(contents)
}

/// # Errors
/// Returns an error if the parse fails
pub fn parse_lp_file(contents: &str) -> anyhow::Result<LPDefinition> {
let mut parsed = LParser::parse(Rule::LP_FILE, contents)?;
let Some(pair) = parsed.next() else {
anyhow::bail!("Invalid LP file");
};
let mut parsed_contents = LPDefinition::default();
for pair in pair.clone().into_inner() {
parsed_contents = build(pair, parsed_contents)?;
}

Ok(dbg!(parsed_contents))
}

#[allow(clippy::unwrap_used)]
fn build_objective(pair: Pair<'_, Rule>) -> anyhow::Result<Objective> {
let mut components = pair.into_inner();
let name = components.next().unwrap().as_str().to_string();
let coefficients: anyhow::Result<Vec<_>> = components.map(|p| p.into_inner().try_into()).collect();
Ok(Objective { name, coefficients: coefficients? })
}

fn build_constraint(_pair: Pair<'_, Rule>) -> anyhow::Result<Constraint> {
// pub name: String,
// pub coefficients: Vec<Coefficient>,
// pub sense: String,
// pub rhs: f64,
unimplemented!()
}

#[allow(clippy::wildcard_enum_match_arm)]
fn build(pair: Pair<'_, Rule>, mut parsed: LPDefinition) -> anyhow::Result<LPDefinition> {
match pair.as_rule() {
// Problem sense
Rule::MIN_SENSE => Ok(parsed.with_sense(Sense::Minimize)),
Rule::MAX_SENSE => Ok(parsed.with_sense(Sense::Maximize)),
// Problem Objectives
Rule::OBJECTIVES => {
let objectives: anyhow::Result<Vec<Objective>> = pair.into_inner().map(|inner_pair| build_objective(inner_pair)).collect();
parsed.add_objective(objectives?);
Ok(parsed)
}
// Problem Constraints
// Rule::CONSTRAINTS => {
// let constraints: anyhow::Result<Vec<Constraint>> = pair.into_inner().map(|inner_pair| build_constraint(inner_pair)).collect();
// parsed.add_constraints(constraints?);
// Ok(parsed)
// }
// Problem Bounds
// Problem Integers
// Problem Generals
// Problem Binaries
_ => Ok(parsed),
}
}

// Rule::CONSTRAINT_EXPR => todo!(),
// Rule::CONSTRAINT_NAME => todo!(),
// Rule::CONSTRAINT => todo!(),
// Rule::CONSTRAINTS => todo!(),
// // Problem Bounds
// Rule::BOUND_PREFIX => todo!(),
// Rule::BOUND => todo!(),
// Rule::BOUNDS => todo!(),
// // Problem Integers
// Rule::INTEGER_PREFIX => todo!(),
// Rule::INTEGERS => todo!(),
// // Problem Generals
// Rule::GENERALS_PREFIX => todo!(),
// Rule::GENERALS => todo!(),
// // Problem Binaries
// Rule::BINARIES_PREFIX => todo!(),
// Rule::BINARIES => todo!(),
// // Other
// Rule::WHITESPACE => todo!(),
// Rule::POS_INFINITY => todo!(),
// Rule::NEG_INFINITY => todo!(),
// Rule::FLOAT => todo!(),
// Rule::PLUS => todo!(),
// Rule::MINUS => todo!(),
// Rule::OPERATOR => todo!(),
// Rule::COLON => todo!(),
// Rule::ASTERIX => todo!(),
// Rule::FREE => todo!(),
// Rule::END => todo!(),
// Rule::COMMENT_TEXT => todo!(),
// Rule::COMMENTS => todo!(),
// Rule::PROBLEM_SENSE => todo!(),
// Rule::VALID_CHARS => todo!(),
// Rule::CONSTRAINT_PREFIX => todo!(),
// Rule::VARIABLE => todo!(),
// Rule::GT => todo!(),
// Rule::GTE => todo!(),
// Rule::LT => todo!(),
// Rule::LTE => todo!(),
// Rule::EQ => todo!(),
// Rule::CMP => todo!(),
// Rule::EOF => todo!(),

0 comments on commit 9de11e1

Please sign in to comment.