Skip to main content

oxilean_std/operations_research/
functions.rs

1//! Auto-generated module
2//!
3//! πŸ€– Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
6
7use super::types::{
8    BanditEnvironment, BellmanFord, Dijkstra, DynamicProgramming, FloydWarshall, FordFulkerson,
9    HungarianSolver, InventoryModel, JobScheduler, KnapsackDP, LagrangianRelaxationSolver,
10    MdpSolver, MultiArmedBanditUcb, NetworkFlowGraph, NewsvendorModel, PrimMst, QueueingSystem,
11    ReliabilitySystem, SimplexSolver, TwoStageStochasticLP,
12};
13
14pub fn app(f: Expr, a: Expr) -> Expr {
15    Expr::App(Box::new(f), Box::new(a))
16}
17pub fn app2(f: Expr, a: Expr, b: Expr) -> Expr {
18    app(app(f, a), b)
19}
20pub fn app3(f: Expr, a: Expr, b: Expr, c: Expr) -> Expr {
21    app(app2(f, a, b), c)
22}
23pub fn cst(s: &str) -> Expr {
24    Expr::Const(Name::str(s), vec![])
25}
26pub fn prop() -> Expr {
27    Expr::Sort(Level::zero())
28}
29pub fn type0() -> Expr {
30    Expr::Sort(Level::succ(Level::zero()))
31}
32pub fn pi(bi: BinderInfo, name: &str, dom: Expr, body: Expr) -> Expr {
33    Expr::Pi(bi, Name::str(name), Box::new(dom), Box::new(body))
34}
35pub fn arrow(a: Expr, b: Expr) -> Expr {
36    pi(BinderInfo::Default, "_", a, b)
37}
38pub fn nat_ty() -> Expr {
39    cst("Nat")
40}
41pub fn real_ty() -> Expr {
42    cst("Real")
43}
44pub fn list_ty(elem: Expr) -> Expr {
45    app(cst("List"), elem)
46}
47pub fn fn_ty(dom: Expr, cod: Expr) -> Expr {
48    arrow(dom, cod)
49}
50pub fn bool_ty() -> Expr {
51    cst("Bool")
52}
53/// `NetworkFlow : Nat -> List (Nat Γ— Nat Γ— Nat) -> Nat -> Nat -> Nat -> Prop`
54/// Max-flow / min-cut predicate for a directed graph with `n` nodes.
55pub fn network_flow_ty() -> Expr {
56    arrow(
57        nat_ty(),
58        arrow(
59            list_ty(nat_ty()),
60            arrow(nat_ty(), arrow(nat_ty(), arrow(nat_ty(), prop()))),
61        ),
62    )
63}
64/// `Scheduling : List (Nat Γ— Nat) -> List Nat -> Prop`
65/// Job scheduling: list of (processing_time, deadline) pairs β†’ feasible schedule.
66pub fn scheduling_ty() -> Expr {
67    let pair = list_ty(nat_ty());
68    arrow(list_ty(pair), arrow(list_ty(nat_ty()), prop()))
69}
70/// `Inventory : Real -> Real -> Real -> Real -> Prop`
71/// EOQ inventory model: demand D, order cost S, holding cost H, lead time L.
72pub fn inventory_ty() -> Expr {
73    arrow(
74        real_ty(),
75        arrow(real_ty(), arrow(real_ty(), arrow(real_ty(), prop()))),
76    )
77}
78/// `QueueingSystem : Real -> Real -> Nat -> Prop`
79/// M/M/c queue with arrival rate Ξ», service rate ΞΌ, and c servers.
80pub fn queuing_ty() -> Expr {
81    arrow(real_ty(), arrow(real_ty(), arrow(nat_ty(), prop())))
82}
83/// `MaxFlowMinCut : Prop`
84/// The max-flow min-cut theorem: max flow value = min cut capacity.
85pub fn max_flow_min_cut_ty() -> Expr {
86    prop()
87}
88/// `FordFulkerson : Prop`
89/// The Ford-Fulkerson theorem: max flow = min cut.
90pub fn ford_fulkerson_ty() -> Expr {
91    prop()
92}
93/// `BellmanOptimality : Prop`
94/// Bellman's principle of optimality for dynamic programming.
95pub fn bellman_optimality_ty() -> Expr {
96    prop()
97}
98/// `LittleLaw : Prop`
99/// Little's law: L = Ξ»W (mean queue length = arrival rate Γ— mean sojourn time).
100pub fn little_law_ty() -> Expr {
101    prop()
102}
103/// `EoqFormula : Prop`
104/// The EOQ formula: Q* = sqrt(2DS / H).
105pub fn eoq_formula_ty() -> Expr {
106    prop()
107}
108/// `LpFeasible : Nat -> Nat -> List Real -> List (List Real) -> List Real -> Prop`
109/// LP feasibility: given n variables, m constraints, objective c, matrix A, rhs b.
110pub fn lp_feasible_ty() -> Expr {
111    arrow(
112        nat_ty(),
113        arrow(
114            nat_ty(),
115            arrow(
116                list_ty(real_ty()),
117                arrow(
118                    list_ty(list_ty(real_ty())),
119                    arrow(list_ty(real_ty()), prop()),
120                ),
121            ),
122        ),
123    )
124}
125/// `SimplexOptimal : Prop`
126/// The simplex method terminates at a basic feasible solution that is optimal
127/// for the LP if one exists.
128pub fn simplex_optimal_ty() -> Expr {
129    prop()
130}
131/// `LpDuality : Prop`
132/// Strong duality: for a primal LP and its dual, if both are feasible then
133/// optimal primal value = optimal dual value.
134pub fn lp_duality_ty() -> Expr {
135    prop()
136}
137/// `RevisedSimplex : Nat -> Nat -> List Real -> Prop`
138/// Revised simplex method for LP with n variables and m constraints.
139pub fn revised_simplex_ty() -> Expr {
140    arrow(nat_ty(), arrow(nat_ty(), arrow(list_ty(real_ty()), prop())))
141}
142/// `ComplementarySlackness : Prop`
143/// Complementary slackness conditions for LP optimality.
144pub fn complementary_slackness_ty() -> Expr {
145    prop()
146}
147/// `IntegerProgramming : Nat -> Nat -> List Real -> Prop`
148/// ILP feasibility / optimality for a mixed-integer program.
149pub fn integer_programming_ty() -> Expr {
150    arrow(nat_ty(), arrow(nat_ty(), arrow(list_ty(real_ty()), prop())))
151}
152/// `BranchAndBound : Prop`
153/// Branch-and-bound algorithm terminates with an optimal integer solution.
154pub fn branch_and_bound_ty() -> Expr {
155    prop()
156}
157/// `CuttingPlane : Prop`
158/// Gomory cutting-plane algorithm: cuts off fractional LP relaxation solutions.
159pub fn cutting_plane_ty() -> Expr {
160    prop()
161}
162/// `BranchAndCut : Prop`
163/// Branch-and-cut combines B&B with cutting planes for MIP.
164pub fn branch_and_cut_ty() -> Expr {
165    prop()
166}
167/// `SetCover : Nat -> List (List Nat) -> List Nat -> Prop`
168/// Set cover: universe of n elements, collection of subsets, selected cover.
169pub fn set_cover_ty() -> Expr {
170    arrow(
171        nat_ty(),
172        arrow(list_ty(list_ty(nat_ty())), arrow(list_ty(nat_ty()), prop())),
173    )
174}
175/// `Knapsack : Nat -> List Nat -> List Nat -> Nat -> Prop`
176/// 0/1 knapsack: capacity, weights, values, optimal value.
177pub fn knapsack_ty() -> Expr {
178    arrow(
179        nat_ty(),
180        arrow(
181            list_ty(nat_ty()),
182            arrow(list_ty(nat_ty()), arrow(nat_ty(), prop())),
183        ),
184    )
185}
186/// `GraphColoring : Nat -> List (Nat Γ— Nat) -> Nat -> Prop`
187/// Graph coloring: n vertices, edge list, chromatic number k.
188pub fn graph_coloring_ty() -> Expr {
189    arrow(nat_ty(), arrow(list_ty(nat_ty()), arrow(nat_ty(), prop())))
190}
191/// `ChromaticNumber : Nat -> Nat -> Prop`
192/// Chromatic number Ο‡(G) = k for graph with n vertices.
193pub fn chromatic_number_ty() -> Expr {
194    arrow(nat_ty(), arrow(nat_ty(), prop()))
195}
196/// `MinimumSpanningTree : Nat -> List (Nat Γ— Nat Γ— Nat) -> Nat -> Prop`
197/// MST: graph with n nodes and weighted edges, optimal total weight.
198pub fn minimum_spanning_tree_ty() -> Expr {
199    arrow(nat_ty(), arrow(list_ty(nat_ty()), arrow(nat_ty(), prop())))
200}
201/// `ShortestPath : Nat -> List (Nat Γ— Nat Γ— Nat) -> Nat -> Nat -> List Nat -> Prop`
202/// Shortest path from source to sink in a weighted graph.
203pub fn shortest_path_ty() -> Expr {
204    arrow(
205        nat_ty(),
206        arrow(
207            list_ty(nat_ty()),
208            arrow(nat_ty(), arrow(nat_ty(), arrow(list_ty(nat_ty()), prop()))),
209        ),
210    )
211}
212/// `DijkstraCorrectness : Prop`
213/// Dijkstra's algorithm computes correct shortest paths on non-negative weights.
214pub fn dijkstra_correctness_ty() -> Expr {
215    prop()
216}
217/// `BellmanFordCorrectness : Prop`
218/// Bellman-Ford detects negative cycles and computes SSSP otherwise.
219pub fn bellman_ford_correctness_ty() -> Expr {
220    prop()
221}
222/// `FloydWarshallCorrectness : Prop`
223/// Floyd-Warshall computes all-pairs shortest paths in O(nΒ³).
224pub fn floyd_warshall_correctness_ty() -> Expr {
225    prop()
226}
227/// `TspTour : Nat -> List (Nat Γ— Nat Γ— Nat) -> List Nat -> Nat -> Prop`
228/// TSP: n cities, distance matrix, Hamiltonian tour, tour length.
229pub fn tsp_tour_ty() -> Expr {
230    arrow(
231        nat_ty(),
232        arrow(
233            list_ty(nat_ty()),
234            arrow(list_ty(nat_ty()), arrow(nat_ty(), prop())),
235        ),
236    )
237}
238/// `TspLowerBound : Prop`
239/// TSP lower bound via minimum spanning tree (Christofides-style bound).
240pub fn tsp_lower_bound_ty() -> Expr {
241    prop()
242}
243/// `VehicleRouting : Nat -> Nat -> List (Nat Γ— Nat Γ— Nat) -> Prop`
244/// VRP: n customers, k vehicles, capacity-constrained routes.
245pub fn vehicle_routing_ty() -> Expr {
246    arrow(nat_ty(), arrow(nat_ty(), arrow(list_ty(nat_ty()), prop())))
247}
248/// `MakespanMinimization : List (List Nat) -> Nat -> Prop`
249/// Parallel machine scheduling: jobs on machines, minimum makespan.
250pub fn makespan_minimization_ty() -> Expr {
251    arrow(list_ty(list_ty(nat_ty())), arrow(nat_ty(), prop()))
252}
253/// `FlowShopScheduling : Nat -> Nat -> List (List Nat) -> Nat -> Prop`
254/// Flow shop: n jobs, m machines, processing times, optimal makespan.
255pub fn flow_shop_scheduling_ty() -> Expr {
256    arrow(
257        nat_ty(),
258        arrow(
259            nat_ty(),
260            arrow(list_ty(list_ty(nat_ty())), arrow(nat_ty(), prop())),
261        ),
262    )
263}
264/// `AssignmentProblem : Nat -> List (List Nat) -> List (Nat Γ— Nat) -> Prop`
265/// Assignment problem: n agents/tasks, cost matrix, optimal assignment.
266pub fn assignment_problem_ty() -> Expr {
267    arrow(
268        nat_ty(),
269        arrow(list_ty(list_ty(nat_ty())), arrow(list_ty(nat_ty()), prop())),
270    )
271}
272/// `HungarianAlgorithm : Prop`
273/// Hungarian algorithm solves the assignment problem in O(nΒ³).
274pub fn hungarian_algorithm_ty() -> Expr {
275    prop()
276}
277/// `QuadraticProgram : Nat -> List Real -> List Real -> Prop`
278/// QP: minimize (1/2) xα΅€Qx + cα΅€x subject to linear constraints.
279pub fn quadratic_program_ty() -> Expr {
280    arrow(
281        nat_ty(),
282        arrow(list_ty(real_ty()), arrow(list_ty(real_ty()), prop())),
283    )
284}
285/// `TwoStageStochastic : Prop`
286/// Two-stage stochastic programming: here-and-now decisions + recourse.
287pub fn two_stage_stochastic_ty() -> Expr {
288    prop()
289}
290/// `ScenarioProgramming : Nat -> List Real -> Prop`
291/// Scenario-based stochastic program with S scenarios and their probabilities.
292pub fn scenario_programming_ty() -> Expr {
293    arrow(nat_ty(), arrow(list_ty(real_ty()), prop()))
294}
295/// `RobustOptimization : Prop`
296/// Min-max robust formulation: minimize worst-case objective over uncertainty set.
297pub fn robust_optimization_ty() -> Expr {
298    prop()
299}
300/// `BellmanEquation : (Nat -> Real) -> (Nat -> Real) -> Prop`
301/// Bellman equation: V(s) = max_a { r(s,a) + Ξ³ V(s') }.
302pub fn bellman_equation_ty() -> Expr {
303    arrow(
304        fn_ty(nat_ty(), real_ty()),
305        arrow(fn_ty(nat_ty(), real_ty()), prop()),
306    )
307}
308/// `MG1Queue : Real -> Real -> Real -> Prop`
309/// M/G/1 queue: arrival rate Ξ», mean service time E[S], variance Var[S].
310pub fn mg1_queue_ty() -> Expr {
311    arrow(real_ty(), arrow(real_ty(), arrow(real_ty(), prop())))
312}
313/// `PollaczekKhinchine : Prop`
314/// Pollaczek-Khinchine mean value formula for M/G/1 queue.
315pub fn pollaczek_khinchine_ty() -> Expr {
316    prop()
317}
318/// `NewsvendorModel : Real -> Real -> Real -> Real -> Prop`
319/// Newsvendor: demand distribution, unit cost, selling price, salvage value.
320pub fn newsvendor_model_ty() -> Expr {
321    arrow(
322        real_ty(),
323        arrow(real_ty(), arrow(real_ty(), arrow(real_ty(), prop()))),
324    )
325}
326/// `SeriesSystem : List Real -> Real -> Prop`
327/// Series system reliability: component reliabilities, system reliability.
328pub fn series_system_ty() -> Expr {
329    arrow(list_ty(real_ty()), arrow(real_ty(), prop()))
330}
331/// `ParallelSystem : List Real -> Real -> Prop`
332/// Parallel system reliability: component reliabilities, system reliability.
333pub fn parallel_system_ty() -> Expr {
334    arrow(list_ty(real_ty()), arrow(real_ty(), prop()))
335}
336/// `EventDrivenSimulation : Nat -> Real -> Prop`
337/// Discrete-event simulation: n events, simulation time horizon.
338pub fn event_driven_simulation_ty() -> Expr {
339    arrow(nat_ty(), arrow(real_ty(), prop()))
340}
341/// `MonteCarloEstimator : Nat -> Real -> Real -> Prop`
342/// Monte Carlo estimation: sample size N, estimate, confidence interval width.
343pub fn monte_carlo_estimator_ty() -> Expr {
344    arrow(nat_ty(), arrow(real_ty(), arrow(real_ty(), prop())))
345}
346/// Build an [`Environment`] containing operations research axioms and theorems.
347pub fn build_operations_research_env() -> Environment {
348    let mut env = Environment::new();
349    let axioms: &[(&str, Expr)] = &[
350        ("NetworkFlow", network_flow_ty()),
351        ("Scheduling", scheduling_ty()),
352        ("Inventory", inventory_ty()),
353        ("QueueingSystem", queuing_ty()),
354        ("MaxFlowMinCut", max_flow_min_cut_ty()),
355        ("ford_fulkerson", ford_fulkerson_ty()),
356        ("bellman_optimality", bellman_optimality_ty()),
357        ("little_law", little_law_ty()),
358        ("eoq_formula", eoq_formula_ty()),
359        ("EdfSchedule", prop()),
360        ("SjfSchedule", prop()),
361        ("DpOptimal", prop()),
362        ("LpFeasible", lp_feasible_ty()),
363        ("simplex_optimal", simplex_optimal_ty()),
364        ("lp_duality", lp_duality_ty()),
365        ("RevisedSimplex", revised_simplex_ty()),
366        ("complementary_slackness", complementary_slackness_ty()),
367        ("IntegerProgramming", integer_programming_ty()),
368        ("branch_and_bound", branch_and_bound_ty()),
369        ("cutting_plane", cutting_plane_ty()),
370        ("branch_and_cut", branch_and_cut_ty()),
371        ("SetCover", set_cover_ty()),
372        ("Knapsack", knapsack_ty()),
373        ("GraphColoring", graph_coloring_ty()),
374        ("ChromaticNumber", chromatic_number_ty()),
375        ("MinimumSpanningTree", minimum_spanning_tree_ty()),
376        ("ShortestPath", shortest_path_ty()),
377        ("dijkstra_correctness", dijkstra_correctness_ty()),
378        ("bellman_ford_correctness", bellman_ford_correctness_ty()),
379        (
380            "floyd_warshall_correctness",
381            floyd_warshall_correctness_ty(),
382        ),
383        ("TspTour", tsp_tour_ty()),
384        ("tsp_lower_bound", tsp_lower_bound_ty()),
385        ("VehicleRouting", vehicle_routing_ty()),
386        ("MakespanMinimization", makespan_minimization_ty()),
387        ("FlowShopScheduling", flow_shop_scheduling_ty()),
388        ("AssignmentProblem", assignment_problem_ty()),
389        ("hungarian_algorithm", hungarian_algorithm_ty()),
390        ("QuadraticProgram", quadratic_program_ty()),
391        ("two_stage_stochastic", two_stage_stochastic_ty()),
392        ("ScenarioProgramming", scenario_programming_ty()),
393        ("robust_optimization", robust_optimization_ty()),
394        ("BellmanEquation", bellman_equation_ty()),
395        ("MG1Queue", mg1_queue_ty()),
396        ("pollaczek_khinchine", pollaczek_khinchine_ty()),
397        ("NewsvendorModel", newsvendor_model_ty()),
398        ("SeriesSystem", series_system_ty()),
399        ("ParallelSystem", parallel_system_ty()),
400        ("EventDrivenSimulation", event_driven_simulation_ty()),
401        ("MonteCarloEstimator", monte_carlo_estimator_ty()),
402    ];
403    for (name, ty) in axioms {
404        env.add(Declaration::Axiom {
405            name: Name::str(*name),
406            univ_params: vec![],
407            ty: ty.clone(),
408        })
409        .ok();
410    }
411    env
412}
413/// Compute binomial coefficient C(n, k).
414pub(super) fn binomial_coeff(n: usize, k: usize) -> u64 {
415    if k > n {
416        return 0;
417    }
418    let k = k.min(n - k);
419    let mut result = 1_u64;
420    for i in 0..k {
421        result = result * (n - i) as u64 / (i + 1) as u64;
422    }
423    result
424}
425/// Branch-and-bound completeness: the algorithm finds the optimal integer solution
426/// in finite time for bounded feasible MIPs
427/// Type: Prop
428pub fn branch_and_bound_completeness_ty() -> Expr {
429    prop()
430}
431/// LP relaxation bound: optimal LP relaxation β‰₯ optimal IP value (minimization)
432/// Type: Prop
433pub fn lp_relaxation_bound_ty() -> Expr {
434    prop()
435}
436/// Gomory cut: valid cutting plane derived from LP tableau for ILP
437/// Type: Nat β†’ List Real β†’ Real β†’ Prop  (pivot row, coefficients, rhs β†’ Gomory cut)
438pub fn gomory_cut_ty() -> Expr {
439    arrow(
440        nat_ty(),
441        arrow(list_ty(real_ty()), arrow(real_ty(), prop())),
442    )
443}
444/// Mixed-integer Gomory cut: Gomory cut for mixed-integer programs
445/// Type: Prop
446pub fn mixed_integer_gomory_cut_ty() -> Expr {
447    prop()
448}
449/// Lift-and-project cuts: stronger cuts for binary programs
450/// Type: Nat β†’ Prop  (variable index β†’ cut for that variable)
451pub fn lift_and_project_cut_ty() -> Expr {
452    arrow(nat_ty(), prop())
453}
454/// Split cut: general cutting plane from a split disjunction
455/// Type: List Real β†’ Real β†’ Prop  (coefficients, rhs β†’ split cut)
456pub fn split_cut_ty() -> Expr {
457    arrow(list_ty(real_ty()), arrow(real_ty(), prop()))
458}
459/// Dantzig-Wolfe decomposition: reformulation by convex combinations of extreme points
460/// Type: Prop
461pub fn dantzig_wolfe_decomposition_ty() -> Expr {
462    prop()
463}
464/// Dantzig-Wolfe restricted master problem: LP with subset of columns
465/// Type: Nat β†’ List Real β†’ Prop  (column count, current columns β†’ RMP)
466pub fn dantzig_wolfe_master_problem_ty() -> Expr {
467    arrow(nat_ty(), arrow(list_ty(real_ty()), prop()))
468}
469/// Benders decomposition: partition variables into master and subproblem
470/// Type: Prop
471pub fn benders_decomposition_ty() -> Expr {
472    prop()
473}
474/// Benders feasibility cut: cut added when subproblem is infeasible
475/// Type: List Real β†’ Real β†’ Prop  (dual ray coefficients, rhs β†’ feasibility cut)
476pub fn benders_feasibility_cut_ty() -> Expr {
477    arrow(list_ty(real_ty()), arrow(real_ty(), prop()))
478}
479/// Benders optimality cut: cut from subproblem optimal dual
480/// Type: List Real β†’ Real β†’ Prop  (dual solution, rhs β†’ optimality cut)
481pub fn benders_optimality_cut_ty() -> Expr {
482    arrow(list_ty(real_ty()), arrow(real_ty(), prop()))
483}
484/// Column generation: pricing problem selects columns with negative reduced cost
485/// Type: (List Real β†’ Real) β†’ Prop  (pricing oracle β†’ column generation valid)
486pub fn column_generation_ty() -> Expr {
487    arrow(fn_ty(list_ty(real_ty()), real_ty()), prop())
488}
489/// Lagrangian relaxation: relax complicating constraints with multipliers
490/// Type: List Real β†’ (List Real β†’ Real) β†’ Real
491///       (Lagrange multipliers, objective β†’ Lagrangian lower bound)
492pub fn lagrangian_relaxation_ty() -> Expr {
493    arrow(
494        list_ty(real_ty()),
495        arrow(fn_ty(list_ty(real_ty()), real_ty()), real_ty()),
496    )
497}
498/// Subgradient method: update Lagrange multipliers using subgradient
499/// Type: List Real β†’ List Real β†’ Real β†’ List Real
500///       (current multipliers, subgradient, step size β†’ updated multipliers)
501pub fn subgradient_update_ty() -> Expr {
502    arrow(
503        list_ty(real_ty()),
504        arrow(list_ty(real_ty()), arrow(real_ty(), list_ty(real_ty()))),
505    )
506}
507/// Lagrangian dual: max over multipliers Ξ» β‰₯ 0 of Lagrangian lower bound
508/// Type: (List Real β†’ Real) β†’ Real  (Lagrangian function β†’ dual bound)
509pub fn lagrangian_dual_ty() -> Expr {
510    arrow(fn_ty(list_ty(real_ty()), real_ty()), real_ty())
511}
512/// Semidefinite program: minimize cΒ·x subject to Ξ£ x_i A_i βͺ° B, A_i symmetric
513/// Type: Nat β†’ List Real β†’ Prop  (matrix size, objective coefficients β†’ SDP)
514pub fn semidefinite_program_ty() -> Expr {
515    arrow(nat_ty(), arrow(list_ty(real_ty()), prop()))
516}
517/// Positive semidefinite constraint: X βͺ° 0 (all eigenvalues β‰₯ 0)
518/// Type: Type β†’ Prop  (matrix type β†’ PSD constraint)
519pub fn psd_constraint_ty() -> Expr {
520    arrow(type0(), prop())
521}
522/// SDP duality: strong duality for semidefinite programs under strict feasibility
523/// Type: Prop
524pub fn sdp_duality_ty() -> Expr {
525    prop()
526}
527/// Second-order cone program: minimize cα΅€x subject to ||A_i x + b_i||β‚‚ ≀ cα΅€x + d
528/// Type: Nat β†’ Nat β†’ Prop  (dimension, number of cone constraints β†’ SOCP)
529pub fn second_order_cone_program_ty() -> Expr {
530    arrow(nat_ty(), arrow(nat_ty(), prop()))
531}
532/// SOCP duality: strong duality for second-order cone programs
533/// Type: Prop
534pub fn socp_duality_ty() -> Expr {
535    prop()
536}
537/// Cone programming general: minimize cα΅€x subject to Ax = b, x ∈ K
538/// Type: Type β†’ Prop  (cone K β†’ cone program over K)
539pub fn cone_program_ty() -> Expr {
540    arrow(type0(), prop())
541}
542/// Minimax regret: minimize worst-case regret over uncertainty set
543/// Type: Type β†’ Prop  (uncertainty set β†’ minimax regret problem)
544pub fn minimax_regret_ty() -> Expr {
545    arrow(type0(), prop())
546}
547/// Box uncertainty set: each parameter varies in interval [a_i - Ξ”_i, a_i + Ξ”_i]
548/// Type: List Real β†’ List Real β†’ Type  (center, radius β†’ box uncertainty set)
549pub fn box_uncertainty_set_ty() -> Expr {
550    arrow(list_ty(real_ty()), arrow(list_ty(real_ty()), type0()))
551}
552/// Ellipsoidal uncertainty set: {u : (u-uβ‚€)α΅€ Σ⁻¹ (u-uβ‚€) ≀ Ω²}
553/// Type: Nat β†’ Real β†’ Type  (dimension, Omega β†’ ellipsoid)
554pub fn ellipsoidal_uncertainty_set_ty() -> Expr {
555    arrow(nat_ty(), arrow(real_ty(), type0()))
556}
557/// Robust counterpart: worst-case formulation for linear program with uncertain data
558/// Type: Type β†’ Prop  (uncertainty set β†’ robust counterpart is tractable)
559pub fn robust_counterpart_ty() -> Expr {
560    arrow(type0(), prop())
561}
562/// Distributionally robust optimization: minimize worst-case expected cost over ambiguity set
563/// Type: Type β†’ Prop  (ambiguity set of distributions β†’ DRO problem)
564pub fn distributionally_robust_optimization_ty() -> Expr {
565    arrow(type0(), prop())
566}
567/// Chance constraint: P(g(x, ΞΎ) ≀ 0) β‰₯ 1 - Ξ΅ for random ΞΎ
568/// Type: Real β†’ Prop  (reliability level 1-Ξ΅ β†’ chance constraint)
569pub fn chance_constraint_ty() -> Expr {
570    arrow(real_ty(), prop())
571}
572/// CVaR (Conditional Value at Risk): expected shortfall above Ξ±-quantile
573/// Type: Real β†’ (Real β†’ Real) β†’ Real  (Ξ±, loss distribution β†’ CVaR)
574pub fn cvar_ty() -> Expr {
575    arrow(real_ty(), arrow(fn_ty(real_ty(), real_ty()), real_ty()))
576}
577/// Sample average approximation (SAA): replace expectation with sample mean
578/// Type: Nat β†’ Prop  (number of scenarios β†’ SAA problem)
579pub fn sample_average_approximation_ty() -> Expr {
580    arrow(nat_ty(), prop())
581}
582/// Progressive hedging: decomposition for stochastic programs
583/// Type: Nat β†’ Real β†’ Prop  (number of scenarios, penalty ρ β†’ PH valid)
584pub fn progressive_hedging_ty() -> Expr {
585    arrow(nat_ty(), arrow(real_ty(), prop()))
586}
587/// MDP: (S, A, P, R, Ξ³) β€” states, actions, transitions, rewards, discount
588/// Type: Nat β†’ Nat β†’ Real β†’ Prop  (|S|, |A|, discount Ξ³ β†’ MDP)
589pub fn markov_decision_process_ty() -> Expr {
590    arrow(nat_ty(), arrow(nat_ty(), arrow(real_ty(), prop())))
591}
592/// Bellman optimality equation: V*(s) = max_a Ξ£ P(s'|s,a)[R(s,a,s') + Ξ³V*(s')]
593/// Type: (Nat β†’ Real) β†’ Prop  (value function V β†’ satisfies Bellman optimality)
594pub fn bellman_optimality_equation_ty() -> Expr {
595    arrow(fn_ty(nat_ty(), real_ty()), prop())
596}
597/// Value iteration convergence: V_k β†’ V* as k β†’ ∞ for Ξ³ < 1
598/// Type: Real β†’ Prop  (discount factor Ξ³ β†’ value iteration converges)
599pub fn value_iteration_convergence_ty() -> Expr {
600    arrow(real_ty(), prop())
601}
602/// Policy iteration convergence: policy iteration terminates in finite steps
603/// Type: Nat β†’ Prop  (|S| β†’ policy iteration terminates in at most |S|! steps)
604pub fn policy_iteration_convergence_ty() -> Expr {
605    arrow(nat_ty(), prop())
606}
607/// Optimal policy: deterministic policy achieving V*
608/// Type: Nat β†’ Nat β†’ Prop  (|S|, |A| β†’ optimal deterministic policy exists)
609pub fn optimal_policy_ty() -> Expr {
610    arrow(nat_ty(), arrow(nat_ty(), prop()))
611}
612/// Q-function (action-value): Q*(s,a) = R(s,a) + Ξ³ Ξ£ P(s'|s,a) V*(s')
613/// Type: Nat β†’ Nat β†’ Real  (state s, action a β†’ Q-value)
614pub fn q_function_ty() -> Expr {
615    arrow(nat_ty(), arrow(nat_ty(), real_ty()))
616}
617/// Contraction mapping in MDP: Bellman operator is a Ξ³-contraction in sup norm
618/// Type: Real β†’ Prop  (Ξ³ β†’ Bellman operator is contraction)
619pub fn bellman_contraction_ty() -> Expr {
620    arrow(real_ty(), prop())
621}
622/// POMDP: partially observable MDP with observation model
623/// Type: Nat β†’ Nat β†’ Nat β†’ Real β†’ Prop  (|S|, |A|, |O|, Ξ³ β†’ POMDP)
624pub fn pomdp_ty() -> Expr {
625    arrow(
626        nat_ty(),
627        arrow(nat_ty(), arrow(nat_ty(), arrow(real_ty(), prop()))),
628    )
629}
630/// Belief state: probability distribution over hidden states
631/// Type: Nat β†’ Type  (|S| β†’ belief simplex type)
632pub fn belief_state_ty() -> Expr {
633    arrow(nat_ty(), type0())
634}
635/// POMDP value function: piecewise-linear convex function of belief state
636/// Type: Nat β†’ Prop  (|S| β†’ PWLC value function exists)
637pub fn pomdp_value_function_ty() -> Expr {
638    arrow(nat_ty(), prop())
639}
640/// Multi-armed bandit: k arms with unknown reward distributions
641/// Type: Nat β†’ Prop  (k arms β†’ MAB problem)
642pub fn multi_armed_bandit_ty() -> Expr {
643    arrow(nat_ty(), prop())
644}
645/// UCB1 algorithm: Upper Confidence Bound policy for MAB
646/// Type: Nat β†’ Real β†’ Nat β†’ Real  (k arms, total plays, arm plays β†’ UCB index)
647pub fn ucb1_index_ty() -> Expr {
648    arrow(nat_ty(), arrow(real_ty(), arrow(nat_ty(), real_ty())))
649}
650/// UCB regret bound: E[regret] = O(√(kT log T)) for UCB1
651/// Type: Nat β†’ Nat β†’ Real  (k, T β†’ regret bound)
652pub fn ucb_regret_bound_ty() -> Expr {
653    arrow(nat_ty(), arrow(nat_ty(), real_ty()))
654}
655/// Thompson sampling: Bayesian approach to MAB using posterior sampling
656/// Type: Nat β†’ Prop  (k arms β†’ Thompson sampling valid)
657pub fn thompson_sampling_ty() -> Expr {
658    arrow(nat_ty(), prop())
659}
660/// Explore-exploit tradeoff: fundamental tension in online learning
661/// Type: Prop
662pub fn explore_exploit_tradeoff_ty() -> Expr {
663    prop()
664}
665/// Register all extended operations research axioms into the environment.
666pub fn register_operations_research_extended(env: &mut Environment) -> Result<(), String> {
667    let axioms: &[(&str, Expr)] = &[
668        (
669            "BranchAndBoundCompleteness",
670            branch_and_bound_completeness_ty(),
671        ),
672        ("LpRelaxationBound", lp_relaxation_bound_ty()),
673        ("GomoryCut", gomory_cut_ty()),
674        ("MixedIntegerGomoryCut", mixed_integer_gomory_cut_ty()),
675        ("LiftAndProjectCut", lift_and_project_cut_ty()),
676        ("SplitCut", split_cut_ty()),
677        (
678            "DantzigWolfeDecomposition",
679            dantzig_wolfe_decomposition_ty(),
680        ),
681        (
682            "DantzigWolfeMasterProblem",
683            dantzig_wolfe_master_problem_ty(),
684        ),
685        ("BendersDecomposition", benders_decomposition_ty()),
686        ("BendersFeasibilityCut", benders_feasibility_cut_ty()),
687        ("BendersOptimalityCut", benders_optimality_cut_ty()),
688        ("ColumnGeneration", column_generation_ty()),
689        ("LagrangianRelaxation", lagrangian_relaxation_ty()),
690        ("SubgradientUpdate", subgradient_update_ty()),
691        ("LagrangianDual", lagrangian_dual_ty()),
692        ("SemidefiniteProgram", semidefinite_program_ty()),
693        ("PSDConstraint", psd_constraint_ty()),
694        ("SDPDuality", sdp_duality_ty()),
695        ("SecondOrderConeProgram", second_order_cone_program_ty()),
696        ("SOCPDuality", socp_duality_ty()),
697        ("ConeProgram", cone_program_ty()),
698        ("MinimaxRegret", minimax_regret_ty()),
699        ("BoxUncertaintySet", box_uncertainty_set_ty()),
700        (
701            "EllipsoidalUncertaintySet",
702            ellipsoidal_uncertainty_set_ty(),
703        ),
704        ("RobustCounterpart", robust_counterpart_ty()),
705        (
706            "DistributionallyRobustOptimization",
707            distributionally_robust_optimization_ty(),
708        ),
709        ("ChanceConstraint", chance_constraint_ty()),
710        ("CVaR", cvar_ty()),
711        (
712            "SampleAverageApproximation",
713            sample_average_approximation_ty(),
714        ),
715        ("ProgressiveHedging", progressive_hedging_ty()),
716        ("MarkovDecisionProcess", markov_decision_process_ty()),
717        (
718            "BellmanOptimalityEquation",
719            bellman_optimality_equation_ty(),
720        ),
721        (
722            "ValueIterationConvergence",
723            value_iteration_convergence_ty(),
724        ),
725        (
726            "PolicyIterationConvergence",
727            policy_iteration_convergence_ty(),
728        ),
729        ("OptimalPolicy", optimal_policy_ty()),
730        ("QFunction", q_function_ty()),
731        ("BellmanContraction", bellman_contraction_ty()),
732        ("POMDP", pomdp_ty()),
733        ("BeliefState", belief_state_ty()),
734        ("POMDPValueFunction", pomdp_value_function_ty()),
735        ("MultiArmedBandit", multi_armed_bandit_ty()),
736        ("UCB1Index", ucb1_index_ty()),
737        ("UCBRegretBound", ucb_regret_bound_ty()),
738        ("ThompsonSampling", thompson_sampling_ty()),
739        ("ExploreExploitTradeoff", explore_exploit_tradeoff_ty()),
740    ];
741    for (name, ty) in axioms {
742        env.add(Declaration::Axiom {
743            name: Name::str(*name),
744            univ_params: vec![],
745            ty: ty.clone(),
746        })
747        .map_err(|e| format!("Failed to add {name}: {e:?}"))?;
748    }
749    Ok(())
750}
751#[cfg(test)]
752mod tests {
753    use super::*;
754    #[test]
755    fn test_queueing_utilization() {
756        let qs = QueueingSystem::new(3.0, 5.0, 1);
757        assert!((qs.utilization() - 0.6).abs() < 1e-9);
758    }
759    #[test]
760    fn test_queueing_mean_queue_length() {
761        let qs = QueueingSystem::new(1.0, 2.0, 1);
762        let l = qs
763            .mean_queue_length_m_m_1()
764            .expect("mean_queue_length_m_m_1 should succeed");
765        assert!((l - 1.0).abs() < 1e-9, "L={l}");
766    }
767    #[test]
768    fn test_network_flow_simple() {
769        let mut g = NetworkFlowGraph::new(4);
770        g.add_edge(0, 1, 10);
771        g.add_edge(0, 2, 10);
772        g.add_edge(1, 3, 10);
773        g.add_edge(2, 3, 10);
774        assert_eq!(g.max_flow_bfs(0, 3), 20);
775    }
776    #[test]
777    fn test_job_scheduler_edf() {
778        let mut sched = JobScheduler::new();
779        sched.add_job("A", 2, 5);
780        sched.add_job("B", 3, 3);
781        sched.add_job("C", 1, 7);
782        let order = sched.earliest_deadline_first();
783        assert_eq!(order, vec!["B", "A", "C"]);
784    }
785    #[test]
786    fn test_dp_knapsack() {
787        let weights = [2, 3, 4, 5];
788        let values = [3, 4, 5, 6];
789        assert_eq!(DynamicProgramming::knapsack(5, &weights, &values), 7);
790    }
791    #[test]
792    fn test_dp_lcs() {
793        let s1 = b"ABCBDAB";
794        let s2 = b"BDCABA";
795        assert_eq!(DynamicProgramming::longest_common_subseq(s1, s2), 4);
796    }
797    #[test]
798    fn test_dp_coin_change() {
799        assert_eq!(
800            DynamicProgramming::coin_change(&[1, 5, 10, 25], 41),
801            Some(4)
802        );
803        assert_eq!(DynamicProgramming::coin_change(&[2], 3), None);
804    }
805    #[test]
806    fn test_inventory_eoq() {
807        let inv = InventoryModel::new(1000.0, 50.0, 5.0, 1.0);
808        let q = inv.eoq();
809        assert!((q - 200.0_f64.sqrt() * 10.0).abs() < 1e-3, "eoq={q}");
810        let tc = inv.total_cost(q);
811        assert!(tc > 0.0);
812    }
813    #[test]
814    fn test_simplex_solver_basic() {
815        let obj = vec![-1.0, -1.0];
816        let a = vec![vec![1.0, 1.0], vec![1.0, 0.0], vec![0.0, 1.0]];
817        let b = vec![4.0, 3.0, 3.0];
818        let solver = SimplexSolver::new(obj, a, b);
819        let val = solver.solve().expect("solve should succeed");
820        assert!((val - (-4.0)).abs() < 1e-6, "simplex val={val}");
821    }
822    #[test]
823    fn test_ford_fulkerson_simple() {
824        let mut ff = FordFulkerson::new(4);
825        ff.add_edge(0, 1, 10);
826        ff.add_edge(0, 2, 10);
827        ff.add_edge(1, 3, 10);
828        ff.add_edge(2, 3, 10);
829        assert_eq!(ff.max_flow(0, 3), 20);
830    }
831    #[test]
832    fn test_hungarian_solver_simple() {
833        let cost = vec![vec![9, 2, 7], vec![3, 6, 1], vec![5, 8, 4]];
834        let (min_cost, assignment) = HungarianSolver::new(cost).solve();
835        assert_eq!(
836            min_cost, 8,
837            "Hungarian cost={min_cost}, assignment={assignment:?}"
838        );
839    }
840    #[test]
841    fn test_bellman_ford_basic() {
842        let mut bf = BellmanFord::new(5);
843        bf.add_edge(0, 1, 6);
844        bf.add_edge(0, 2, 7);
845        bf.add_edge(1, 2, 8);
846        bf.add_edge(1, 3, 5);
847        bf.add_edge(1, 4, -4);
848        bf.add_edge(2, 3, -3);
849        bf.add_edge(2, 4, 9);
850        bf.add_edge(3, 1, -2);
851        bf.add_edge(4, 0, 2);
852        bf.add_edge(4, 3, 7);
853        let dist = bf.shortest_paths(0).expect("shortest_paths should succeed");
854        assert_eq!(dist[0], 0);
855        assert_eq!(dist[1], 2);
856        assert_eq!(dist[2], 7);
857    }
858    #[test]
859    fn test_knapsack_dp_with_selection() {
860        let solver = KnapsackDP::new(5, vec![2, 3, 4, 5], vec![3, 4, 5, 6]);
861        let (val, selected) = solver.solve();
862        assert_eq!(val, 7, "knapsack value={val}");
863        assert!(
864            selected.contains(&0) && selected.contains(&1),
865            "selected={selected:?}"
866        );
867    }
868    #[test]
869    fn test_dijkstra_basic() {
870        let mut g = Dijkstra::new(5);
871        g.add_edge(0, 1, 10);
872        g.add_edge(0, 3, 5);
873        g.add_edge(1, 2, 1);
874        g.add_edge(3, 1, 3);
875        g.add_edge(3, 2, 9);
876        g.add_edge(2, 4, 4);
877        g.add_edge(3, 4, 2);
878        let dist = g.shortest_paths(0);
879        assert_eq!(dist[0], 0);
880        assert_eq!(dist[3], 5);
881        assert_eq!(dist[1], 8);
882        assert_eq!(dist[4], 7);
883    }
884    #[test]
885    fn test_floyd_warshall_basic() {
886        let mut fw = FloydWarshall::new(4);
887        fw.add_edge(0, 1, 3);
888        fw.add_edge(0, 3, 7);
889        fw.add_edge(1, 0, 8);
890        fw.add_edge(1, 2, 2);
891        fw.add_edge(2, 0, 5);
892        fw.add_edge(2, 3, 1);
893        fw.add_edge(3, 0, 2);
894        let d = fw.run().expect("run should succeed");
895        assert_eq!(d[0][2], 5);
896        assert_eq!(d[3][1], 5);
897    }
898    #[test]
899    fn test_prim_mst() {
900        let mut prim = PrimMst::new(4);
901        prim.add_edge(0, 1, 1);
902        prim.add_edge(0, 2, 4);
903        prim.add_edge(1, 2, 2);
904        prim.add_edge(1, 3, 5);
905        prim.add_edge(2, 3, 3);
906        let (total, _edges) = prim.run();
907        assert_eq!(total, 6, "MST weight={total}");
908    }
909    #[test]
910    fn test_newsvendor_optimal_quantity() {
911        let nv = NewsvendorModel::new(0.0, 100.0, 5.0, 10.0, 2.0);
912        let q = nv.optimal_quantity();
913        assert!((q - 62.5).abs() < 1e-6, "Q*={q}");
914    }
915    #[test]
916    fn test_reliability_series() {
917        let sys = ReliabilitySystem::new(vec![0.9, 0.8, 0.95]);
918        let r = sys.series_reliability();
919        assert!((r - 0.9 * 0.8 * 0.95).abs() < 1e-9, "series R={r}");
920    }
921    #[test]
922    fn test_reliability_parallel() {
923        let sys = ReliabilitySystem::new(vec![0.9, 0.9]);
924        let r = sys.parallel_reliability();
925        assert!((r - 0.99).abs() < 1e-9, "parallel R={r}");
926    }
927    #[test]
928    fn test_build_env_has_axioms() {
929        let env = build_operations_research_env();
930        assert!(env.get(&Name::str("ford_fulkerson")).is_some());
931        assert!(env.get(&Name::str("simplex_optimal")).is_some());
932        assert!(env.get(&Name::str("branch_and_bound")).is_some());
933        assert!(env.get(&Name::str("dijkstra_correctness")).is_some());
934        assert!(env.get(&Name::str("hungarian_algorithm")).is_some());
935        assert!(env.get(&Name::str("robust_optimization")).is_some());
936        assert!(env.get(&Name::str("pollaczek_khinchine")).is_some());
937        assert!(env.get(&Name::str("MonteCarloEstimator")).is_some());
938    }
939    #[test]
940    fn test_extended_env_has_axioms() {
941        use oxilean_kernel::Environment;
942        let mut env = Environment::new();
943        register_operations_research_extended(&mut env).expect("Environment::new should succeed");
944        let names = [
945            "BranchAndBoundCompleteness",
946            "GomoryCut",
947            "DantzigWolfeDecomposition",
948            "BendersDecomposition",
949            "ColumnGeneration",
950            "LagrangianRelaxation",
951            "SemidefiniteProgram",
952            "SecondOrderConeProgram",
953            "MinimaxRegret",
954            "ChanceConstraint",
955            "MarkovDecisionProcess",
956            "BellmanOptimalityEquation",
957            "ValueIterationConvergence",
958            "PolicyIterationConvergence",
959            "POMDP",
960            "MultiArmedBandit",
961            "UCB1Index",
962            "ThompsonSampling",
963        ];
964        for name in &names {
965            assert!(
966                env.get(&Name::str(*name)).is_some(),
967                "Extended axiom '{name}' not found"
968            );
969        }
970    }
971    #[test]
972    fn test_mdp_value_iteration_simple() {
973        let n_states = 2;
974        let n_actions = 2;
975        let discount = 0.9_f64;
976        let rewards = vec![vec![-1.0, 0.0], vec![0.0, 1.0]];
977        let transitions = vec![
978            vec![vec![1.0, 0.0], vec![0.0, 1.0]],
979            vec![vec![0.0, 1.0], vec![0.0, 1.0]],
980        ];
981        let mdp = MdpSolver::new(n_states, n_actions, discount, rewards, transitions);
982        let (v, _iters) = mdp.value_iteration(1e-6, 1000);
983        assert!(
984            v[1] > v[0],
985            "V(good state) should exceed V(bad state): V={v:?}"
986        );
987    }
988    #[test]
989    fn test_mdp_policy_extraction() {
990        let n_states = 2;
991        let n_actions = 2;
992        let discount = 0.9_f64;
993        let rewards = vec![vec![0.0, 0.5], vec![0.0, 1.0]];
994        let transitions = vec![
995            vec![vec![1.0, 0.0], vec![0.0, 1.0]],
996            vec![vec![0.0, 1.0], vec![0.0, 1.0]],
997        ];
998        let mdp = MdpSolver::new(n_states, n_actions, discount, rewards, transitions);
999        let (v, _) = mdp.value_iteration(1e-6, 1000);
1000        let policy = mdp.extract_policy(&v);
1001        assert_eq!(policy[1], 1, "At state 1, should choose action 1");
1002    }
1003    #[test]
1004    fn test_ucb_bandit_selects_best() {
1005        let means = vec![0.2, 0.5, 0.8];
1006        let mut env = BanditEnvironment::new(means.clone());
1007        assert_eq!(env.optimal_arm(), 2, "Optimal arm should be 2");
1008        let regret = env.run_ucb1(200);
1009        assert!(regret >= 0.0, "Regret should be non-negative, got {regret}");
1010        assert!(regret.is_finite(), "Regret should be finite, got {regret}");
1011    }
1012    #[test]
1013    fn test_ucb1_index_infinite_for_unplayed() {
1014        let bandit = MultiArmedBanditUcb::new(3);
1015        assert_eq!(
1016            bandit.ucb_index(0),
1017            f64::INFINITY,
1018            "Unplayed arm should have infinite UCB"
1019        );
1020        assert_eq!(bandit.select_arm(), 0, "Should select first unplayed arm");
1021    }
1022    #[test]
1023    fn test_lagrangian_solver_polyak_step() {
1024        let mut solver = LagrangianRelaxationSolver::new(2, 1.0);
1025        let subgradient = [0.5, -0.3];
1026        let step = solver.polyak_step(10.0, 8.0, &subgradient);
1027        assert!(step > 0.0, "Polyak step should be positive, got {step}");
1028        solver.subgradient_update(&subgradient, step);
1029        assert!(
1030            solver.multipliers[0] > 0.0,
1031            "Multiplier 0 should increase: {}",
1032            solver.multipliers[0]
1033        );
1034        assert!(
1035            solver.multipliers[1] >= 0.0,
1036            "Multiplier 1 should be non-negative: {}",
1037            solver.multipliers[1]
1038        );
1039    }
1040    #[test]
1041    fn test_two_stage_stochastic_cost() {
1042        let model = TwoStageStochasticLP::new(
1043            vec![2.0],
1044            vec![3.0],
1045            vec![0.4, 0.6],
1046            vec![vec![5.0], vec![7.0]],
1047        );
1048        let x = vec![1.0];
1049        let y = vec![vec![2.0], vec![3.0]];
1050        let expected_recourse = 0.4 * 6.0 + 0.6 * 9.0;
1051        let recourse = model.expected_recourse_cost(&y);
1052        assert!(
1053            (recourse - expected_recourse).abs() < 1e-9,
1054            "Expected recourse={expected_recourse}, got {recourse}"
1055        );
1056        let total = model.total_cost(&x, &y);
1057        assert!(
1058            (total - (2.0 + expected_recourse)).abs() < 1e-9,
1059            "Total cost={total}"
1060        );
1061        assert!(model.is_stage1_feasible(&x), "x=[1.0] should be feasible");
1062    }
1063}