1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
//! LP, convex QP, and conic programming — the `pounce-convex` interior-point
//! path, re-exported (feature `convex`).
//!
//! ```toml
//! [dependencies]
//! pounce-rs = { version = "0.9", features = ["convex"] }
//! ```
//!
//! The problem is [`QpProblem`] in the standard form
//!
//! ```text
//! minimize ½ xᵀP x + cᵀx
//! subject to A x = b (equality)
//! G x ≤ h (inequality)
//! lb ≤ x ≤ ub (first-class variable box)
//! ```
//!
//! with `P` supplied as its lower triangle in [`Triplet`] form (an empty `P`
//! is an LP). Cone blocks beyond the nonnegative orthant — second-order,
//! exponential, power, PSD — are declared with [`ConeSpec`] and solved by
//! [`solve_socp_ipm`].
//!
//! Every entry point takes a linear-solver factory; [`crate::linsol::backend`]
//! is the default one.
//!
//! ```
//! use pounce_rs::convex::{QpOptions, QpProblem, QpStatus, Triplet, solve_qp_ipm};
//! use pounce_rs::linsol::backend;
//!
//! // min ‖x‖² − 0.5·x0 − 1.5·x1 s.t. x0 + x1 == 1, 0 ≤ x ≤ 5
//! let prob = QpProblem {
//! n: 2,
//! p_lower: vec![Triplet::new(0, 0, 2.0), Triplet::new(1, 1, 2.0)],
//! c: vec![-0.5, -1.5],
//! a: vec![Triplet::new(0, 0, 1.0), Triplet::new(0, 1, 1.0)],
//! b: vec![1.0],
//! g: vec![],
//! h: vec![],
//! lb: vec![0.0, 0.0],
//! ub: vec![5.0, 5.0],
//! };
//!
//! let sol = solve_qp_ipm(&prob, &QpOptions::default(), backend);
//! assert_eq!(sol.status, QpStatus::Optimal);
//! assert!((sol.x[0] - 0.25).abs() < 1e-5 && (sol.x[1] - 0.75).abs() < 1e-5);
//! ```
//!
//! ## Batched solves
//!
//! [`solve_qp_batch`] solves a slice of independent instances serially (each
//! factor parallel internally); [`solve_qp_batch_parallel`] runs one instance
//! per rayon worker, which is the faster arrangement for many small QPs —
//! pass [`crate::linsol::serial_backend`] there so the workers don't
//! oversubscribe. [`solve_qp_batch_parallel_warm`] seeds each instance from a
//! [`QpWarmStart`], and [`solve_qp_multi_rhs`] handles one problem under many
//! right-hand sides.
//!
//! When the instances share a *fixed* sparsity pattern and differ only in
//! their numbers, [`QpFactorization`] performs the AMD ordering and symbolic
//! analysis once and reuses it across solves.
//!
//! ```
//! use pounce_rs::convex::{QpOptions, QpProblem, QpStatus, Triplet, solve_qp_batch_parallel};
//! use pounce_rs::linsol::serial_backend;
//!
//! // The same box QP under many linear terms: min ‖x‖² − 2·tᵀx, 0 ≤ x ≤ 1.
//! let probs: Vec<QpProblem> = [0.25, 0.5, 2.0]
//! .iter()
//! .map(|t| QpProblem {
//! n: 1,
//! p_lower: vec![Triplet::new(0, 0, 2.0)],
//! c: vec![-2.0 * t],
//! a: vec![],
//! b: vec![],
//! g: vec![],
//! h: vec![],
//! lb: vec![0.0],
//! ub: vec![1.0],
//! })
//! .collect();
//!
//! let sols = solve_qp_batch_parallel(&probs, &QpOptions::default(), serial_backend);
//! assert!(sols.iter().all(|s| s.status == QpStatus::Optimal));
//! assert!((sols[0].x[0] - 0.25).abs() < 1e-6);
//! assert!((sols[2].x[0] - 1.0).abs() < 1e-6); // clamped by the box
//! ```
//!
//! ## Sensitivity
//!
//! [`QpSensitivity`] differentiates a solved QP with respect to its data and
//! [`ReducedHessian`] gives the curvature on the null space of the active
//! constraints — the QP counterpart of [`crate::sensitivity`] on the NLP
//! path.
//!
//! ## Beyond the curated surface
//!
//! [`pounce_convex`] itself is re-exported, so the modules not listed here
//! (`crossover`, `presolve`, `hsde`, `equilibrate`, …) stay reachable
//! without adding a dependency.
pub use ;
/// The underlying crate, for anything not surfaced above.
pub use pounce_convex;