Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adding implementation for Objectives #2

Merged
merged 1 commit into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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> {

Check warning on line 49 in src/parse.rs

View workflow job for this annotation

GitHub Actions / test

function `build_constraint` is never used

Check warning on line 49 in src/parse.rs

View workflow job for this annotation

GitHub Actions / test

function `build_constraint` is never used
// 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!(),
Loading
Loading