Skip to content

Commit

Permalink
refactor: Weekend tidy
Browse files Browse the repository at this point in the history
  • Loading branch information
dandxy89 committed Jan 28, 2024
1 parent ea37bf2 commit 7b55028
Show file tree
Hide file tree
Showing 37 changed files with 1,015 additions and 938 deletions.
1 change: 0 additions & 1 deletion src/lib.rs
Expand Up @@ -24,7 +24,6 @@ use pest_derive::Parser;

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

#[derive(Parser)]
Expand Down
149 changes: 0 additions & 149 deletions src/lp_parts.rs

This file was deleted.

37 changes: 36 additions & 1 deletion src/model/constraint.rs
@@ -1,4 +1,13 @@
use crate::model::{coefficient::Coefficient, sense::Cmp, sos::SOSClass};
use std::str::FromStr;

use pest::iterators::Pair;
use unique_id::sequence::SequenceGenerator;

use crate::{
common::RuleExt,
model::{coefficient::Coefficient, get_name, lp_problem::LPPart, sense::Cmp, sos::SOSClass},
Rule,
};

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
Expand Down Expand Up @@ -46,3 +55,29 @@ impl Constraint {
}
}
}

#[allow(clippy::unwrap_used)]
impl LPPart for Constraint {
type Output = Constraint;

fn try_into(pair: Pair<'_, Rule>, gen: &mut SequenceGenerator) -> anyhow::Result<Self> {
let mut parts = pair.into_inner().peekable();
// Constraint name can be omitted in LP files, so we need to handle that case
let name = get_name(&mut parts, gen, Rule::CONSTRAINT_NAME);
let mut coefficients: Vec<_> = vec![];
while let Some(p) = parts.peek() {
if p.as_rule().is_cmp() {
break;
}
coefficients.push(parts.next().unwrap());
}
let coefficients: anyhow::Result<Vec<_>> = coefficients
.into_iter()
.filter(|p| !matches!(p.as_rule(), Rule::PLUS | Rule::MINUS))
.map(|p| p.into_inner().try_into())
.collect();
let sense = Cmp::from_str(parts.next().unwrap().as_str())?;
let rhs = parts.next().unwrap().as_str().parse()?;
Ok(Constraint::Standard { name, coefficients: coefficients?, sense, rhs })
}
}
23 changes: 19 additions & 4 deletions src/model/lp_problem.rs
@@ -1,6 +1,21 @@
use std::collections::{hash_map::Entry, HashMap};

use crate::model::{constraint::Constraint, objective::Objective, sense::Sense, variable::VariableType};
use pest::iterators::Pair;
use unique_id::sequence::SequenceGenerator;

use crate::{
model::{constraint::Constraint, objective::Objective, sense::Sense, variable::Variable},
Rule,
};

pub trait LPPart
where
Self: Sized,
{
type Output;

fn try_into(pair: Pair<'_, Rule>, gen: &mut SequenceGenerator) -> anyhow::Result<Self::Output>;
}

#[derive(Debug, Default, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
Expand All @@ -11,7 +26,7 @@ use crate::model::{constraint::Constraint, objective::Objective, sense::Sense, v
pub struct LPProblem {
pub problem_name: String,
pub problem_sense: Sense,
pub variables: HashMap<String, VariableType>,
pub variables: HashMap<String, Variable>,
pub objectives: Vec<Objective>,
pub constraints: HashMap<String, Constraint>,
}
Expand All @@ -33,10 +48,10 @@ impl LPProblem {
}
}

pub fn set_var_bounds(&mut self, name: &str, kind: VariableType) {
pub fn set_variable_bounds(&mut self, name: &str, kind: Variable) {
if !name.is_empty() {
match self.variables.entry(name.to_string()) {
Entry::Occupied(k) if matches!(kind, VariableType::SemiContinuous) => {
Entry::Occupied(k) if matches!(kind, Variable::SemiContinuous) => {
k.into_mut().set_semi_continuous();
}
Entry::Occupied(k) => *k.into_mut() = kind,
Expand Down
17 changes: 17 additions & 0 deletions src/model/mod.rs
@@ -1,7 +1,24 @@
use std::iter::Peekable;

use pest::iterators::Pairs;
use unique_id::{sequence::SequenceGenerator, Generator};

use crate::{model::prefix::Prefix, Rule};

pub mod coefficient;
pub mod constraint;
pub mod lp_problem;
pub mod objective;
pub mod parse_model;
pub mod prefix;
pub mod sense;
pub mod sos;
pub mod variable;

fn get_name(parts: &mut Peekable<Pairs<'_, Rule>>, gen: &mut SequenceGenerator, rule: Rule) -> String {
if parts.peek().unwrap().as_rule() == rule {
parts.next().unwrap().as_str().to_string()
} else {
format!("{}{}", rule.prefix(), gen.next_id())
}
}
20 changes: 19 additions & 1 deletion src/model/objective.rs
@@ -1,4 +1,10 @@
use crate::model::coefficient::Coefficient;
use pest::iterators::Pair;
use unique_id::sequence::SequenceGenerator;

use crate::{
model::{coefficient::Coefficient, get_name, lp_problem::LPPart},
Rule,
};

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
Expand All @@ -10,3 +16,15 @@ pub struct Objective {
pub name: String,
pub coefficients: Vec<Coefficient>,
}

impl LPPart for Objective {
type Output = Objective;

fn try_into(pair: Pair<'_, Rule>, gen: &mut SequenceGenerator) -> anyhow::Result<Self> {
let mut parts = pair.into_inner().peekable();
// Objective name can be omitted in LP files, so we need to handle that case
let name = get_name(&mut parts, gen, Rule::OBJECTIVE_NAME);
let coefficients: anyhow::Result<Vec<_>> = parts.map(|p| p.into_inner().try_into()).collect();
Ok(Objective { name, coefficients: coefficients? })
}
}
62 changes: 62 additions & 0 deletions src/model/parse_model.rs
@@ -0,0 +1,62 @@
use pest::iterators::Pair;
use unique_id::sequence::SequenceGenerator;

use crate::{
model::{
constraint::Constraint,
lp_problem::{LPPart, LPProblem},
objective::Objective,
sense::Sense,
sos::SOSClass,
variable::get_bound,
},
Rule,
};

type ResultVec<T> = anyhow::Result<Vec<T>>;

#[allow(clippy::wildcard_enum_match_arm)]
/// # Errors
/// Returns an error if the `compose` fails
pub fn compose(pair: Pair<'_, Rule>, mut parsed: LPProblem, gen: &mut SequenceGenerator) -> anyhow::Result<LPProblem> {
match pair.as_rule() {
// Problem Name
Rule::PROBLEM_NAME => return Ok(parsed.with_problem_name(pair.as_str())),
// Problem sense
Rule::MIN_SENSE => return Ok(parsed.with_sense(Sense::Minimize)),
Rule::MAX_SENSE => return Ok(parsed.with_sense(Sense::Maximize)),
// Problem Objectives
Rule::OBJECTIVES => {
let parts: ResultVec<_> = pair.into_inner().map(|p| <Objective as LPPart>::try_into(p, gen)).collect();
parsed.add_objective(parts?);
}
// Problem Constraints
Rule::CONSTRAINTS => {
let parts: ResultVec<_> = pair.into_inner().map(|p| <Constraint as LPPart>::try_into(p, gen)).collect();
parsed.add_constraints(parts?);
}
Rule::SOS => {
let parts: ResultVec<_> = pair.into_inner().map(|p| <SOSClass as LPPart>::try_into(p, gen)).collect();
parsed.add_constraints(parts?);
}
// Problem Bounds
Rule::BOUNDS => {
for bound_pair in pair.into_inner() {
if let Some((name, kind)) = get_bound(bound_pair) {
parsed.set_variable_bounds(name, kind);
}
}
}
// Variable Bounds
r @ (Rule::INTEGERS | Rule::GENERALS | Rule::BINARIES | Rule::SEMI_CONTINUOUS) => {
for p in pair.into_inner() {
if matches!(p.as_rule(), Rule::VARIABLE) {
parsed.set_variable_bounds(p.as_str(), r.into());
}
}
}
// Otherwise, skip!
_ => (),
}
Ok(parsed)
}
15 changes: 15 additions & 0 deletions src/model/prefix.rs
@@ -0,0 +1,15 @@
use crate::Rule;

pub trait Prefix {
fn prefix(&self) -> &'static str;
}

impl Prefix for Rule {
fn prefix(&self) -> &'static str {
match self {
Self::OBJECTIVE_NAME => "obj_",
Self::CONSTRAINT_NAME => "con_",
_ => "",
}
}
}

0 comments on commit 7b55028

Please sign in to comment.