1use crate::{OptimizationError, OptimizationResult};
30
31use amari_tropical::{TropicalMatrix, TropicalNumber};
32
33use num_traits::Float;
34use std::marker::PhantomData;
35
36#[derive(Clone, Debug)]
38pub struct TropicalConfig<T: Float> {
39 pub max_iterations: usize,
41 pub tolerance: T,
43 pub use_acceleration: bool,
45 pub epsilon: T,
47 pub enable_trace: bool,
49}
50
51impl<T: Float> Default for TropicalConfig<T> {
52 fn default() -> Self {
53 Self {
54 max_iterations: 1000,
55 tolerance: T::from(1e-12).unwrap(),
56 use_acceleration: true,
57 epsilon: T::from(1e-15).unwrap(),
58 enable_trace: false,
59 }
60 }
61}
62
63#[derive(Clone, Debug)]
65pub struct TropicalResult<T: Float> {
66 pub solution: Vec<TropicalNumber<T>>,
68 pub objective_value: TropicalNumber<T>,
70 pub iterations: usize,
72 pub converged: bool,
74 pub eigenvalue: Option<TropicalNumber<T>>,
76 pub trace: Option<Vec<Vec<TropicalNumber<T>>>>,
78}
79
80pub trait TropicalObjective<T: Float> {
82 fn evaluate(&self, x: &[TropicalNumber<T>]) -> TropicalNumber<T>;
84
85 fn is_tropical_convex(&self) -> bool {
87 false }
89
90 fn tropical_subgradient(&self, _x: &[TropicalNumber<T>]) -> Option<Vec<TropicalNumber<T>>> {
92 None }
94}
95
96pub trait TropicalConstraint<T: Float> {
98 fn evaluate_constraint(&self, x: &[TropicalNumber<T>]) -> Vec<TropicalNumber<T>>;
100
101 fn is_tropical_linear(&self) -> bool {
103 false
104 }
105}
106
107#[derive(Clone, Debug)]
109pub struct TropicalOptimizer<T: Float> {
110 config: TropicalConfig<T>,
111 _phantom: PhantomData<T>,
112}
113
114impl<T: Float> TropicalOptimizer<T> {
115 pub fn new(config: TropicalConfig<T>) -> Self {
117 Self {
118 config,
119 _phantom: PhantomData,
120 }
121 }
122
123 pub fn with_default_config() -> Self {
125 Self::new(TropicalConfig::default())
126 }
127
128 pub fn solve_tropical_linear_program(
130 &self,
131 objective: &[TropicalNumber<T>],
132 _constraint_matrix: &TropicalMatrix<T>,
133 _constraint_rhs: &[TropicalNumber<T>],
134 ) -> OptimizationResult<TropicalResult<T>> {
135 let n = objective.len();
136
137 let mut best_idx = 0;
140 let mut best_value = objective[0];
141
142 for (i, &obj_val) in objective.iter().enumerate() {
143 if obj_val.value() < best_value.value() {
144 best_value = obj_val;
145 best_idx = i;
146 }
147 }
148
149 let mut solution = vec![TropicalNumber::tropical_zero(); n];
151 solution[best_idx] = TropicalNumber::tropical_one();
152
153 Ok(TropicalResult {
154 solution,
155 objective_value: best_value,
156 iterations: 1,
157 converged: true,
158 eigenvalue: None,
159 trace: None,
160 })
161 }
162
163 pub fn solve_tropical_eigenvalue(
165 &self,
166 matrix: &TropicalMatrix<T>,
167 ) -> OptimizationResult<TropicalResult<T>> {
168 let n = matrix.rows();
169 if n != matrix.cols() {
170 return Err(OptimizationError::InvalidProblem {
171 message: "Matrix must be square for eigenvalue computation".to_string(),
172 });
173 }
174
175 let mut eigenvector = vec![TropicalNumber::tropical_one(); n];
177
178 for iteration in 0..self.config.max_iterations {
179 let mut new_eigenvector = vec![TropicalNumber::tropical_zero(); n];
180
181 for (i, new_elem) in new_eigenvector.iter_mut().enumerate().take(n) {
183 for (j, &eigen_val) in eigenvector.iter().enumerate().take(n) {
184 if let Some(matrix_ij) = matrix.get(i, j) {
185 let product = matrix_ij.tropical_mul(eigen_val);
186 *new_elem = new_elem.tropical_add(product);
187 }
188 }
189 }
190
191 let mut eigenvalue = new_eigenvector[0];
193 for &val in &new_eigenvector[1..] {
194 if val.value() > eigenvalue.value() {
195 eigenvalue = val;
196 }
197 }
198
199 let mut converged = true;
201 for i in 0..n {
202 if (eigenvector[i].value() - new_eigenvector[i].value()).abs()
203 > self.config.tolerance
204 {
205 converged = false;
206 break;
207 }
208 }
209
210 eigenvector = new_eigenvector;
211
212 if converged {
213 return Ok(TropicalResult {
214 solution: eigenvector,
215 objective_value: eigenvalue,
216 iterations: iteration + 1,
217 converged: true,
218 eigenvalue: Some(eigenvalue),
219 trace: None,
220 });
221 }
222 }
223
224 Err(OptimizationError::ConvergenceFailure {
225 iterations: self.config.max_iterations,
226 })
227 }
228
229 pub fn solve_tropical_convex(
231 &self,
232 objective: &impl TropicalObjective<T>,
233 initial_point: Vec<TropicalNumber<T>>,
234 ) -> OptimizationResult<TropicalResult<T>> {
235 if !objective.is_tropical_convex() {
236 return Err(OptimizationError::InvalidProblem {
237 message: "Objective function is not tropical convex".to_string(),
238 });
239 }
240
241 let mut solution = initial_point;
242 let mut best_value = objective.evaluate(&solution);
243
244 for iteration in 0..self.config.max_iterations {
246 let mut improved = false;
247
248 for i in 0..solution.len() {
249 let old_value = solution[i];
250
251 let perturbation = TropicalNumber::new(T::from(0.1).unwrap());
253 solution[i] = old_value.tropical_add(perturbation);
254
255 let new_obj_value = objective.evaluate(&solution);
256 if new_obj_value.value() < best_value.value() {
257 best_value = new_obj_value;
258 improved = true;
259 } else {
260 solution[i] = old_value; }
262 }
263
264 if !improved {
265 return Ok(TropicalResult {
266 solution,
267 objective_value: best_value,
268 iterations: iteration + 1,
269 converged: true,
270 eigenvalue: None,
271 trace: None,
272 });
273 }
274 }
275
276 Err(OptimizationError::ConvergenceFailure {
277 iterations: self.config.max_iterations,
278 })
279 }
280
281 pub fn solve_shortest_path(
283 &self,
284 distance_matrix: &TropicalMatrix<T>,
285 source: usize,
286 target: usize,
287 ) -> OptimizationResult<TropicalResult<T>> {
288 let n = distance_matrix.rows();
289 if n != distance_matrix.cols() || source >= n || target >= n {
290 return Err(OptimizationError::InvalidProblem {
291 message: "Invalid distance matrix or node indices".to_string(),
292 });
293 }
294
295 let shortest_distance = distance_matrix
298 .get(source, target)
299 .unwrap_or(TropicalNumber::tropical_zero());
300
301 let path = if source == target {
303 vec![TropicalNumber::new(T::from(source as f64).unwrap())]
304 } else {
305 vec![
306 TropicalNumber::new(T::from(source as f64).unwrap()),
307 TropicalNumber::new(T::from(target as f64).unwrap()),
308 ]
309 };
310
311 Ok(TropicalResult {
312 solution: path,
313 objective_value: shortest_distance,
314 iterations: 1,
315 converged: true,
316 eigenvalue: None,
317 trace: None,
318 })
319 }
320}
321
322pub mod scheduling {
324 use super::*;
325
326 #[derive(Clone, Debug)]
328 pub struct TropicalScheduler<T: Float> {
329 pub task_durations: Vec<TropicalNumber<T>>,
331 pub precedence_matrix: TropicalMatrix<T>,
333 pub resource_limits: Vec<TropicalNumber<T>>,
335 }
336
337 impl<T: Float> TropicalScheduler<T> {
338 pub fn new(
340 task_durations: Vec<TropicalNumber<T>>,
341 precedence_matrix: TropicalMatrix<T>,
342 resource_limits: Vec<TropicalNumber<T>>,
343 ) -> Self {
344 Self {
345 task_durations,
346 precedence_matrix,
347 resource_limits,
348 }
349 }
350
351 pub fn solve_schedule(
353 &self,
354 optimizer: &TropicalOptimizer<T>,
355 ) -> OptimizationResult<Vec<TropicalNumber<T>>> {
356 let result = optimizer.solve_tropical_eigenvalue(&self.precedence_matrix)?;
358
359 let start_times = result.solution;
361
362 Ok(start_times)
363 }
364
365 pub fn compute_critical_path(
367 &self,
368 optimizer: &TropicalOptimizer<T>,
369 ) -> OptimizationResult<Vec<usize>> {
370 let schedule = self.solve_schedule(optimizer)?;
371
372 let mut critical_path = Vec::new();
374 for (i, &start_time) in schedule.iter().enumerate() {
375 if !start_time.is_zero() {
376 critical_path.push(i);
378 }
379 }
380
381 Ok(critical_path)
382 }
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389 use approx::assert_relative_eq;
390
391 #[test]
392 fn test_tropical_optimizer_creation() {
393 let config = TropicalConfig::<f64>::default();
394 let _optimizer = TropicalOptimizer::new(config);
395
396 let _default_optimizer = TropicalOptimizer::<f64>::with_default_config();
397 }
398
399 #[test]
400 fn test_tropical_linear_program_simple() {
401 let optimizer = TropicalOptimizer::<f64>::with_default_config();
402
403 let objective = vec![TropicalNumber::new(1.0), TropicalNumber::new(2.0)];
405
406 let matrix_data = vec![vec![0.0, 1.0], vec![1.0, 0.0]];
407 let constraint_matrix = TropicalMatrix::from_log_probs(&matrix_data);
408
409 let constraint_rhs = vec![TropicalNumber::new(3.0), TropicalNumber::new(4.0)];
410
411 let result = optimizer.solve_tropical_linear_program(
412 &objective,
413 &constraint_matrix,
414 &constraint_rhs,
415 );
416
417 assert!(result.is_ok());
418 let solution = result.unwrap();
419 assert!(solution.converged);
420 assert_eq!(solution.solution.len(), 2);
421 }
422
423 #[test]
424 fn test_tropical_eigenvalue_simple() {
425 let optimizer = TropicalOptimizer::<f64>::with_default_config();
426
427 let matrix_data = vec![vec![0.0, 2.0], vec![1.0, 0.0]];
429 let matrix = TropicalMatrix::from_log_probs(&matrix_data);
430
431 let result = optimizer.solve_tropical_eigenvalue(&matrix);
432
433 match result {
434 Ok(solution) => {
435 assert!(solution.converged);
436 assert!(solution.eigenvalue.is_some());
437 assert_eq!(solution.solution.len(), 2);
438 }
439 Err(_e) => {
440 }
444 }
445 }
446
447 #[test]
448 fn test_shortest_path() {
449 let optimizer = TropicalOptimizer::<f64>::with_default_config();
450
451 let distances = vec![
453 vec![0.0, 2.0, 5.0],
454 vec![2.0, 0.0, 1.0],
455 vec![5.0, 1.0, 0.0],
456 ];
457 let distance_matrix = TropicalMatrix::from_log_probs(&distances);
458
459 let result = optimizer.solve_shortest_path(&distance_matrix, 0, 2);
460
461 assert!(result.is_ok());
462 let path_result = result.unwrap();
463 assert!(path_result.converged);
464 assert_relative_eq!(path_result.objective_value.value(), 5.0, epsilon = 1e-10);
466 }
467
468 #[test]
469 fn test_tropical_scheduler() {
470 let task_durations = vec![
471 TropicalNumber::new(2.0),
472 TropicalNumber::new(3.0),
473 TropicalNumber::new(1.0),
474 ];
475
476 let precedence_data = vec![
477 vec![0.0, 2.0, f64::NEG_INFINITY],
478 vec![f64::NEG_INFINITY, 0.0, 3.0],
479 vec![f64::NEG_INFINITY, f64::NEG_INFINITY, 0.0],
480 ];
481 let precedence_matrix = TropicalMatrix::from_log_probs(&precedence_data);
482
483 let resource_limits = vec![TropicalNumber::new(10.0)];
484
485 let scheduler =
486 scheduling::TropicalScheduler::new(task_durations, precedence_matrix, resource_limits);
487
488 let optimizer = TropicalOptimizer::<f64>::with_default_config();
489 let schedule = scheduler.solve_schedule(&optimizer);
490
491 assert!(schedule.is_ok());
492 let start_times = schedule.unwrap();
493 assert_eq!(start_times.len(), 3);
494 }
495
496 struct SimpleObjective<T: Float> {
498 _phantom: PhantomData<T>,
499 }
500
501 impl<T: Float> TropicalObjective<T> for SimpleObjective<T> {
502 fn evaluate(&self, x: &[TropicalNumber<T>]) -> TropicalNumber<T> {
503 let mut result = TropicalNumber::tropical_zero();
505 for &val in x {
506 result = result.tropical_add(val);
507 }
508 result
509 }
510
511 fn is_tropical_convex(&self) -> bool {
512 true
513 }
514 }
515
516 #[test]
517 fn test_tropical_convex_optimization() {
518 let optimizer = TropicalOptimizer::<f64>::with_default_config();
519 let objective = SimpleObjective {
520 _phantom: PhantomData,
521 };
522
523 let initial_point = vec![TropicalNumber::new(1.0), TropicalNumber::new(2.0)];
524
525 let result = optimizer.solve_tropical_convex(&objective, initial_point);
526
527 assert!(result.is_ok());
528 let solution = result.unwrap();
529 assert_eq!(solution.solution.len(), 2);
530 }
531}