lpsolve 1.0.1

High-level lpsolve wrapper
Documentation
use std::fmt;
use std::error::Error;

/// Matrix operation types for error reporting
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MatrixOp {
    Get,
    Set,
    GetColumn,
    SetColumn,
    GetRow,
    SetRow,
}

impl MatrixOp {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Get => "get",
            Self::Set => "set",
            Self::GetColumn => "get_column",
            Self::SetColumn => "set_column",
            Self::GetRow => "get_row",
            Self::SetRow => "set_row",
        }
    }
}

/// Context for operations - uses static strings to avoid allocations
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum OpContext {
    AddColumn,
    AddConstraint,
    SetObjective,
    SetBounds,
    SetInteger,
    SetBinary,
    SetUnbounded,
    SetSemicont,
    SetVariableType,
    SetRowName,
    SetColName,
    SetProblemName,
    SetUpbo,
    SetLowbo,
    MatrixGet,
    MatrixSet,
    Resize,
    DeleteColumn,
    DeleteRow,
    GetColumn,
    SetColumn,
    GetRow,
    SetRow,
    GetSensitivityRhs,
    GetSensitivityObj,
    Validate,
    GetSolution,
}

impl OpContext {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::AddColumn => "add_column",
            Self::AddConstraint => "add_constraint",
            Self::SetObjective => "set_objective_function",
            Self::SetBounds => "set_bounds",
            Self::SetInteger => "set_integer",
            Self::SetBinary => "set_binary",
            Self::SetUnbounded => "set_unbounded",
            Self::SetSemicont => "set_semicont",
            Self::SetVariableType => "set_variable_type",
            Self::SetRowName => "set_row_name",
            Self::SetColName => "set_col_name",
            Self::SetProblemName => "set_problem_name",
            Self::SetUpbo => "set_upbo",
            Self::SetLowbo => "set_lowbo",
            Self::MatrixGet => "get_mat",
            Self::MatrixSet => "set_mat",
            Self::Resize => "resize",
            Self::DeleteColumn => "del_column",
            Self::DeleteRow => "del_constraint",
            Self::GetColumn => "get_column",
            Self::SetColumn => "set_column",
            Self::GetRow => "get_row",
            Self::SetRow => "set_row",
            Self::GetSensitivityRhs => "get_sensitivity_rhs",
            Self::GetSensitivityObj => "get_sensitivity_obj",
            Self::Validate => "is_feasible",
            Self::GetSolution => "get_solution_variables",
        }
    }
}

/// Entity types for naming errors
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum EntityType {
    Row,
    Column,
    Problem,
}

impl EntityType {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Row => "row",
            Self::Column => "column",
            Self::Problem => "problem",
        }
    }
}

/// Main error type for lpsolve operations - avoids heap allocations where possible
#[derive(Debug, Clone, PartialEq)]
pub enum LpSolveError {
    /// Failed to create a new problem instance (usually out of memory)
    CreationFailed,

    /// Failed to read a problem from file
    FileReadError {
        format: FileFormat,
    },

    /// Failed to write a problem
    WriteError,

    /// Operation failed due to invalid dimensions
    DimensionMismatch {
        expected: usize,
        actual: usize,
        context: OpContext,
    },

    /// Index out of bounds
    IndexOutOfBounds {
        index: i32,
        min: i32,
        max: i32,
        context: OpContext,
    },

    /// Failed to add a constraint
    ConstraintAdditionFailed {
        row: Option<i32>,
    },

    /// Failed to add a column
    ColumnOperationFailed {
        column: Option<i32>,
        context: OpContext,
    },

    /// Failed to set objective function
    ObjectiveFunctionError,

    /// Matrix operation failed
    MatrixOperationFailed {
        row: i32,
        col: i32,
        op: MatrixOp,
    },

    /// Memory allocation failed
    OutOfMemory,

    /// Problem is in add_rowmode, operation not allowed
    InAddRowMode,

    /// Basis operation failed
    BasisOperationFailed,

    /// Name setting/getting failed
    NamingError {
        entity: EntityType,
        index: i32,
    },

    /// SOS constraint error
    SOSConstraintError,

    /// Solution not available (solve not called or failed)
    NoSolutionAvailable,

    /// Invalid name (contains null bytes)
    InvalidName {
        context: &'static str,
    },

    /// Buffer too small for operation
    BufferTooSmall {
        required: usize,
        provided: usize,
        context: OpContext,
    },
}

impl fmt::Display for LpSolveError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::CreationFailed =>
                write!(f, "Failed to create lpsolve problem instance"),

            Self::FileReadError { format } =>
                write!(f, "Failed to read {:?} file", format),

            Self::WriteError =>
                write!(f, "Failed to write problem"),

            Self::DimensionMismatch { expected, actual, context } =>
                write!(f, "Dimension mismatch in {}: expected {} elements, got {}",
                       context.as_str(), expected, actual),

            Self::IndexOutOfBounds { index, min, max, context } =>
                write!(f, "Index {} out of bounds [{}, {}] in {}",
                       index, min, max, context.as_str()),

            Self::ConstraintAdditionFailed { row } =>
                if let Some(r) = row {
                    write!(f, "Failed to add constraint at row {}", r)
                } else {
                    write!(f, "Failed to add constraint")
                },

            Self::ColumnOperationFailed { column, context } =>
                if let Some(c) = column {
                    write!(f, "Column operation '{}' failed for column {}", context.as_str(), c)
                } else {
                    write!(f, "Column operation '{}' failed", context.as_str())
                },

            Self::ObjectiveFunctionError =>
                write!(f, "Failed to set objective function"),

            Self::MatrixOperationFailed { row, col, op } =>
                write!(f, "Matrix operation '{}' failed at ({}, {})",
                       op.as_str(), row, col),

            Self::OutOfMemory =>
                write!(f, "Out of memory"),

            Self::InAddRowMode =>
                write!(f, "Operation not allowed while in add_row mode"),

            Self::BasisOperationFailed =>
                write!(f, "Basis operation failed"),

            Self::NamingError { entity, index } =>
                write!(f, "Failed to set/get name for {} at index {}",
                       entity.as_str(), index),

            Self::SOSConstraintError =>
                write!(f, "SOS constraint error"),

            Self::NoSolutionAvailable =>
                write!(f, "No solution available (solve not called or failed)"),

            Self::InvalidName { context } =>
                write!(f, "Invalid name in {} (contains null bytes)", context),

            Self::BufferTooSmall { required, provided, context } =>
                write!(f, "Buffer too small for {}: required {} elements, provided {}",
                       context.as_str(), required, provided),
        }
    }
}

impl Error for LpSolveError {}

/// File format for reading/writing problems
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FileFormat {
    LP,
    FreeMPS,
    FixedMPS,
}

/// Result type alias for lpsolve operations
pub type Result<T> = std::result::Result<T, LpSolveError>;

/// Helper for C return codes (1 = success, 0 = failure for most lpsolve functions)
pub(crate) trait CReturnCode {
    fn check_with<E: Into<LpSolveError>>(self, error: E) -> Result<()>;
}

impl CReturnCode for libc::c_int {
    fn check_with<E: Into<LpSolveError>>(self, error: E) -> Result<()> {
        if self == 1 {
            Ok(())
        } else {
            Err(error.into())
        }
    }
}

impl CReturnCode for libc::c_uchar {
    fn check_with<E: Into<LpSolveError>>(self, error: E) -> Result<()> {
        if self == 1 {
            Ok(())
        } else {
            Err(error.into())
        }
    }
}