ommx 3.0.0-beta.1

Open Mathematical prograMming eXchange (OMMX)
Documentation
use super::*;
use crate::{Constraint, ConstraintID, DecisionVariable, Function, ParameterTable, VariableID};
use std::collections::BTreeMap;

impl Instance {
    pub fn new(
        sense: Sense,
        objective: Function,
        decision_variables: BTreeMap<VariableID, DecisionVariable>,
        constraints: BTreeMap<ConstraintID, Constraint>,
    ) -> crate::Result<Self> {
        Self::builder()
            .sense(sense)
            .objective(objective)
            .decision_variables(decision_variables)
            .constraints(constraints)
            .build()
    }
}

impl ParametricInstance {
    pub fn new(
        sense: Sense,
        objective: Function,
        decision_variables: BTreeMap<VariableID, DecisionVariable>,
        parameters: ParameterTable,
        constraints: BTreeMap<ConstraintID, Constraint>,
    ) -> crate::Result<Self> {
        Self::builder()
            .sense(sense)
            .objective(objective)
            .decision_variables(decision_variables)
            .parameters(parameters)
            .constraints(constraints)
            .build()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{
        coeff,
        constraint::{Constraint, ConstraintID},
        linear, DecisionVariable, VariableID,
    };
    use maplit::btreemap;

    fn parameters(ids: impl IntoIterator<Item = VariableID>) -> ParameterTable {
        ParameterTable::from_ids(ids.into_iter().collect())
    }

    #[test]
    fn test_instance_new_fails_with_undefined_variable_in_objective() {
        // Create decision variables that do not include variable ID 999
        let decision_variables = btreemap! {
            VariableID::from(1) => DecisionVariable::binary(),
            VariableID::from(2) => DecisionVariable::binary(),
        };

        // Create objective function that uses undefined variable ID 999
        let objective = (linear!(999) + coeff!(1.0)).into();

        let constraints = BTreeMap::new();

        // This should fail because variable ID 999 is used in objective but not defined
        insta::assert_snapshot!(
            Instance::new(
                Sense::Minimize,
                objective,
                decision_variables,
                constraints,
            )
            .unwrap_err(),
            @r#"Undefined variable ID is used: VariableID(999)"#
        );
    }

    #[test]
    fn test_instance_new_fails_with_undefined_variable_in_constraint() {
        // Create decision variables that do not include variable ID 999
        let decision_variables = btreemap! {
            VariableID::from(1) => DecisionVariable::binary(),
            VariableID::from(2) => DecisionVariable::binary(),
        };

        // Create simple objective function using defined variables
        let objective = (linear!(1) + coeff!(1.0)).into();

        // Create constraint that uses undefined variable ID 999
        let constraints = btreemap! {
            ConstraintID::from(1) => Constraint::equal_to_zero((linear!(999) + coeff!(1.0)).into()),
        };

        // This should fail because variable ID 999 is used in constraint but not defined
        insta::assert_snapshot!(
            Instance::new(
                Sense::Minimize,
                objective,
                decision_variables,
                constraints,
            )
            .unwrap_err(),
            @r#"Undefined variable ID is used: VariableID(999)"#
        );
    }

    #[test]
    fn test_parametric_instance_new_succeeds() {
        // Test successful creation with decision variables and parameters in both objective and constraints
        let decision_variables = btreemap! {
            VariableID::from(1) => DecisionVariable::binary(),
            VariableID::from(2) => DecisionVariable::binary(),
        };

        let parameters = parameters([VariableID::from(100), VariableID::from(101)]);

        // Objective function uses both decision variables and parameters
        let objective = ((linear!(1) + linear!(100)).unwrap() + coeff!(1.0))
            .unwrap()
            .into();

        // Constraints also use both decision variables and parameters
        let constraints = btreemap! {
            ConstraintID::from(1) => Constraint::equal_to_zero(((linear!(2) + linear!(101)).unwrap() + coeff!(1.0)).unwrap().into()),
            ConstraintID::from(2) => Constraint::less_than_or_equal_to_zero(((linear!(1) + linear!(100)).unwrap() + coeff!(2.0)).unwrap().into()),
        };

        let parametric_instance = ParametricInstance::new(
            Sense::Maximize,
            objective,
            decision_variables,
            parameters,
            constraints,
        )
        .unwrap();

        assert_eq!(*parametric_instance.sense(), Sense::Maximize);
        assert_eq!(parametric_instance.decision_variables().len(), 2);
        assert_eq!(parametric_instance.parameters().len(), 2);
        assert_eq!(parametric_instance.constraints().len(), 2);
    }

    #[test]
    fn test_parametric_instance_new_fails_with_duplicated_variable_id() {
        // Test detection of ID collision between decision variables and parameters
        let decision_variables = btreemap! {
            VariableID::from(1) => DecisionVariable::binary(),
            VariableID::from(2) => DecisionVariable::binary(),
        };

        // Parameter with same ID as decision variable
        let parameters = parameters([VariableID::from(1), VariableID::from(100)]);

        let objective = (linear!(1) + coeff!(1.0)).into();
        let constraints = BTreeMap::new();

        insta::assert_snapshot!(
            ParametricInstance::new(
                Sense::Minimize,
                objective,
                decision_variables,
                parameters,
                constraints,
            )
            .unwrap_err(),
            @"Duplicated variable ID is found in definition: VariableID(1)"
        );
    }

    #[test]
    fn test_parametric_instance_new_fails_with_undefined_variable_in_objective() {
        // Test detection of undefined variable ID in objective function
        let decision_variables = btreemap! {
            VariableID::from(1) => DecisionVariable::binary(),
            VariableID::from(2) => DecisionVariable::binary(),
        };

        let parameters = parameters([VariableID::from(100)]);

        // Objective function uses undefined variable ID 999
        let objective = (linear!(999) + coeff!(1.0)).into();

        let constraints = BTreeMap::new();

        insta::assert_snapshot!(
            ParametricInstance::new(
                Sense::Minimize,
                objective,
                decision_variables,
                parameters,
                constraints,
            )
            .unwrap_err(),
            @r#"Undefined variable ID is used: VariableID(999)"#
        );
    }

    #[test]
    fn test_parametric_instance_new_fails_with_undefined_variable_in_constraint() {
        // Test detection of undefined variable ID in constraint
        let decision_variables = btreemap! {
            VariableID::from(1) => DecisionVariable::binary(),
            VariableID::from(2) => DecisionVariable::binary(),
        };

        let parameters = parameters([VariableID::from(100)]);

        let objective = (linear!(1) + coeff!(1.0)).into();

        // Constraint uses undefined variable ID 999
        let constraints = btreemap! {
            ConstraintID::from(1) => Constraint::equal_to_zero((linear!(999) + coeff!(1.0)).into()),
        };

        insta::assert_snapshot!(
            ParametricInstance::new(
                Sense::Minimize,
                objective,
                decision_variables,
                parameters,
                constraints,
            )
            .unwrap_err(),
            @r#"Undefined variable ID is used: VariableID(999)"#
        );
    }
}