rust-or / rust-lp-modeler

Lp modeler written in Rust
MIT License
95 stars 29 forks source link

Must problem variables be of type "&str"? #62

Open heartsh opened 3 years ago

heartsh commented 3 years ago

Hi Developer,

I am writing the software that solves mixture integer programming. In this software, an objective function maximizes the sum of the scores of possible certain index pairs(, as you show as an example of an assignment problem). I noticed that your assignment problem example shows the error "Cannot open file" when I replace the type of men and women (&str) with u8. (e.g., men: "A", "B", "C" -> 1, 2, 3, women: "D", "E", "F" -> 4, 5, 6.) Am I forced to use type "&str"?

Thank you in advance.

jcavat commented 3 years ago

Hello heartsh, It should work if you transform u8 to str when you give a unique identifier for the variables : LpBinary::new(&format!("{}_{}", m,w));

Can you provide a minimalist snippet we can test ?

heartsh commented 3 years ago

The code below (which slightly modifies your assignment problem example) gives the error "thread 'main' panicked at 'Cannot open file', examples/assignment.rs:64:5" when I execute "cargo run --example assignment":

extern crate lp_modeler;

use std::collections::HashMap;

use lp_modeler::dsl::*;
use lp_modeler::solvers::{SolverTrait, CbcSolver};

fn main() {
    // Problem Data
    let men = vec![1, 2, 3];
    let women = vec![4, 5, 6];
    let compatibility_score: HashMap<(u8, u8),f32> = vec![
        ((1, 4), 50.0),
        ((1, 5), 75.0),
        ((1, 6), 75.0),
        ((2, 4), 60.0),
        ((2, 5), 95.0),
        ((2, 6), 80.0),
        ((3, 4), 60.0),
        ((3, 5), 70.0),
        ((3, 6), 80.0),
    ].into_iter().collect();

    // Define Problem
    let mut problem = LpProblem::new("Matchmaking", LpObjective::Maximize);

    // Define Variables
    let vars: HashMap<(u8,u8), LpBinary> =
        men.iter()
            .flat_map(|&m| women.iter()
            .map(move |&w| {
                let key = (m,w);
                let value = LpBinary::new(&format!("{}_{}", m,w));
                (key, value)
            }))
            .collect();

    // Define Objective Function
    let obj_vec: Vec<LpExpression> = {
       vars.iter().map( |(&(m,w), bin)| {
           let &coef = compatibility_score.get(&(m, w)).unwrap();
           coef * bin
       } )
    }.collect();
    problem += obj_vec.sum();

    // Define Constraints
    // - constraint 1: Each man must be assigned to exactly one woman
    for &m in &men{
        problem += sum(&women, |&w| vars.get(&(m,w)).unwrap() ).equal(1);
    }

    // - constraint 2: Each woman must be assigned to exactly one man
    for &w in &women{
        problem += sum(&men, |&m| vars.get(&(m,w)).unwrap() ).equal(1);
    }

    // Run Solver
    let solver = CbcSolver::new();
    let result = solver.run(&problem);

    // Compute final objective function value
    // (terminate if error, or assign status & variable values)
    assert!(result.is_ok(), result.unwrap_err());
    let solution = result.unwrap();
    let mut obj_value = 0f32;
    for (&(m, w), var) in &vars{
        let obj_coef = compatibility_score.get(&(m, w)).unwrap();
        let var_value = solution.results.get(&var.name).unwrap();

        obj_value += obj_coef * var_value;
    }

    // Print output
    println!("Status: {:?}", solution.status);
    println!("Objective Value: {}", obj_value);
    for (var_name, var_value) in &solution.results{
        let int_var_value = *var_value as u32;
        if int_var_value == 1{
            println!("{} = {}", var_name, int_var_value);
        }
    }
}

However, it works when I replaced

let value = LpBinary::new(&format!("{}_{}", m,w));

with

let value = LpBinary::new(&format!("A{}_B{}", m,w));

resulting

Status: Optimal
Objective Value: 230
A3_B4 = 1
A2_B5 = 1
A1_B6 = 1

My CBC MILP solver is version 2.10.5.

jcavat commented 3 years ago

I got it. It's to the underlying file format. The variable name should start by a letter and must respect some rules (typically +-^/ ). Mhmmm. I will mention rules for naming convention and try to find a way to crash with a better indication.