Skip to main content

otspot_core/
error.rs

1/// MPSファイルのパース中に発生するエラー
2#[non_exhaustive]
3#[derive(Debug)]
4pub enum MpsError {
5    /// ファイル読み込み時のI/Oエラー
6    IoError(std::io::Error),
7    /// 指定行のパースエラー(行番号とメッセージを含む)
8    ParseError { line: usize, message: String },
9    /// 必須セクションが欠落している
10    MissingSection(String),
11    /// 同じセクションが複数回出現した
12    DuplicateSection(String),
13    /// 無効な行タイプ文字が指定された
14    InvalidRowType(char),
15    /// 無効な上下限タイプ文字列が指定された
16    InvalidBoundType(String),
17    /// 未定義の行名または列名が参照された
18    UndefinedReference { kind: String, name: String },
19    /// INTORG マーカーが対応する INTEND で閉じられないまま COLUMNS を抜けた。
20    /// 残り全列が無警告で整数化されるのを防ぐため明示エラーとする。
21    UnclosedIntegerMarker,
22}
23
24impl std::fmt::Display for MpsError {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        match self {
27            MpsError::IoError(e) => write!(f, "I/O error: {}", e),
28            MpsError::ParseError { line, message } => {
29                write!(f, "Parse error at line {}: {}", line, message)
30            }
31            MpsError::MissingSection(s) => write!(f, "Missing required section: {}", s),
32            MpsError::DuplicateSection(s) => write!(f, "Duplicate section: {}", s),
33            MpsError::InvalidRowType(c) => write!(f, "Invalid row type: {}", c),
34            MpsError::InvalidBoundType(s) => write!(f, "Invalid bound type: {}", s),
35            MpsError::UndefinedReference { kind, name } => {
36                write!(f, "Undefined {} reference: {}", kind, name)
37            }
38            MpsError::UnclosedIntegerMarker => {
39                write!(f, "INTORG marker not closed by a matching INTEND in COLUMNS")
40            }
41        }
42    }
43}
44
45impl std::error::Error for MpsError {}
46
47impl From<std::io::Error> for MpsError {
48    fn from(err: std::io::Error) -> Self {
49        MpsError::IoError(err)
50    }
51}
52
53/// ソルバー全体の統一エラー型
54///
55/// 入力検証・数値計算・MPSパースなど、ソルバー操作中に
56/// 発生しうるエラーを統一的に表現する。
57///
58/// 注意: Infeasible/Unbounded/MaxIterations は数学的結果であり、
59/// エラーではないため [`SolveStatus`](crate::problem::SolveStatus) で表現する。
60#[non_exhaustive]
61#[derive(Debug)]
62pub enum SolverError {
63    /// MPSファイルのパースエラー
64    Mps(MpsError),
65
66    /// 次元不一致(配列長・行列サイズの不整合)
67    ///
68    /// 例: `c.len() != a.ncols`, トリプレット配列の長さ不一致
69    DimensionMismatch {
70        /// どのフィールド/配列が不一致か(例: "c", "b", "triplet_arrays")
71        field: &'static str,
72        /// 期待されるサイズ
73        expected: usize,
74        /// 実際のサイズ
75        got: usize,
76    },
77
78    /// インデックスが有効範囲外
79    ///
80    /// 例: 行列の列インデックス、基底列インデックス
81    IndexOutOfBounds {
82        /// どのインデックスか(例: "column", "row", "basis_column")
83        context: &'static str,
84        /// 範囲外のインデックス値
85        index: usize,
86        /// 上限値(0..bound が有効範囲)
87        bound: usize,
88    },
89
90    /// 基底行列が特異(LU分解で数値的に特異なピボットを検出)
91    SingularBasis {
92        /// 特異性が検出されたガウス消去のステップ番号
93        step: usize,
94    },
95
96    /// 空の入力が渡された
97    EmptyInput {
98        /// どの入力が空か(例: "basis")
99        context: &'static str,
100    },
101
102    /// Deadline を超過した(タイムアウト)
103    DeadlineExceeded,
104
105    /// 非有限係数(NaN または ±∞)が入力された
106    ///
107    /// 例: `c[i]` が NaN、`b[j]` が Inf、行列要素が NaN
108    NonFiniteCoefficient {
109        /// どのフィールドか("c", "b", "A" など)
110        field: &'static str,
111        /// 最初に非有限値が検出されたインデックス
112        index: usize,
113    },
114
115    /// 変数境界が無効(NaN または lb > ub)
116    InvalidBounds {
117        /// 無効な境界を持つ変数のインデックス
118        index: usize,
119        /// 下限値
120        lb: f64,
121        /// 上限値
122        ub: f64,
123    },
124}
125
126impl std::fmt::Display for SolverError {
127    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128        match self {
129            SolverError::Mps(e) => write!(f, "{}", e),
130            SolverError::DimensionMismatch { field, expected, got } => {
131                write!(f, "Dimension mismatch: {} expected {} but got {}", field, expected, got)
132            }
133            SolverError::IndexOutOfBounds { context, index, bound } => {
134                write!(f, "{} index {} out of bounds (size={})", context, index, bound)
135            }
136            SolverError::SingularBasis { step } => {
137                write!(f, "Singular matrix detected at step {}", step)
138            }
139            SolverError::EmptyInput { context } => {
140                write!(f, "Empty input: {}", context)
141            }
142            SolverError::DeadlineExceeded => {
143                write!(f, "Deadline exceeded during computation")
144            }
145            SolverError::NonFiniteCoefficient { field, index } => {
146                write!(f, "Non-finite coefficient in {}: index {}", field, index)
147            }
148            SolverError::InvalidBounds { index, lb, ub } => {
149                write!(f, "Invalid bounds at index {}: lb={} > ub={} or NaN", index, lb, ub)
150            }
151        }
152    }
153}
154
155impl std::error::Error for SolverError {}
156
157impl From<MpsError> for SolverError {
158    fn from(e: MpsError) -> Self {
159        SolverError::Mps(e)
160    }
161}