Skip to main content

oxilean_std/variational_analysis/
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    ADMMSolver, ConvexSubdifferential, EkelandPrinciple, FunctionSequence, MetricRegularityChecker,
9    MordukhovichSubdiffApprox, MountainPassConfig, ProxFnType, ProxRegularSet, ProximalOperator,
10    ProximalPointSolver,
11};
12
13pub fn app(f: Expr, a: Expr) -> Expr {
14    Expr::App(Box::new(f), Box::new(a))
15}
16pub fn app2(f: Expr, a: Expr, b: Expr) -> Expr {
17    app(app(f, a), b)
18}
19pub fn app3(f: Expr, a: Expr, b: Expr, c: Expr) -> Expr {
20    app(app2(f, a, b), c)
21}
22pub fn cst(s: &str) -> Expr {
23    Expr::Const(Name::str(s), vec![])
24}
25pub fn prop() -> Expr {
26    Expr::Sort(Level::zero())
27}
28pub fn type0() -> Expr {
29    Expr::Sort(Level::succ(Level::zero()))
30}
31pub fn pi(bi: BinderInfo, name: &str, dom: Expr, body: Expr) -> Expr {
32    Expr::Pi(bi, Name::str(name), Box::new(dom), Box::new(body))
33}
34pub fn arrow(a: Expr, b: Expr) -> Expr {
35    pi(BinderInfo::Default, "_", a, b)
36}
37pub fn bvar(n: u32) -> Expr {
38    Expr::BVar(n)
39}
40pub fn real_ty() -> Expr {
41    cst("Real")
42}
43pub fn nat_ty() -> Expr {
44    cst("Nat")
45}
46pub fn list_ty(elem: Expr) -> Expr {
47    app(cst("List"), elem)
48}
49pub fn bool_ty() -> Expr {
50    cst("Bool")
51}
52pub fn fn_ty(dom: Expr, cod: Expr) -> Expr {
53    arrow(dom, cod)
54}
55pub fn vec_ty() -> Expr {
56    list_ty(real_ty())
57}
58pub fn seq_ty(elem: Expr) -> Expr {
59    fn_ty(nat_ty(), elem)
60}
61/// `RegularSubdifferential : (List Real -> Real) -> List Real -> (List Real -> Prop)`
62/// F-subdifferential (Fréchet): v ∈ D̂f(x) iff liminf_{y→x} (f(y)-f(x)-⟨v,y-x⟩)/‖y-x‖ ≥ 0.
63pub fn regular_subdifferential_ty() -> Expr {
64    arrow(
65        fn_ty(vec_ty(), real_ty()),
66        arrow(vec_ty(), fn_ty(vec_ty(), prop())),
67    )
68}
69/// `LimitingSubdifferential : (List Real -> Real) -> List Real -> (List Real -> Prop)`
70/// Mordukhovich (basic) subdifferential: cluster points of regular subgradients along sequences.
71pub fn limiting_subdifferential_ty() -> Expr {
72    arrow(
73        fn_ty(vec_ty(), real_ty()),
74        arrow(vec_ty(), fn_ty(vec_ty(), prop())),
75    )
76}
77/// `ClarkeSubdifferential : (List Real -> Real) -> List Real -> (List Real -> Prop)`
78/// ∂^C f(x) = conv(∂_L f(x)): convex hull of limiting subdifferential.
79pub fn clarke_subdifferential_ty() -> Expr {
80    arrow(
81        fn_ty(vec_ty(), real_ty()),
82        arrow(vec_ty(), fn_ty(vec_ty(), prop())),
83    )
84}
85/// `ClarkeGeneralizedGradient : (List Real -> Real) -> List Real -> List Real`
86/// A selection from Clarke subdifferential (not unique in general).
87pub fn clarke_generalized_gradient_ty() -> Expr {
88    arrow(fn_ty(vec_ty(), real_ty()), fn_ty(vec_ty(), vec_ty()))
89}
90/// `MordukhovichCriterion : (List Real -> Prop) -> List Real -> Prop`
91/// Mordukhovich normal cone regularity criterion for closed sets.
92pub fn mordukhovich_criterion_ty() -> Expr {
93    arrow(fn_ty(vec_ty(), prop()), arrow(vec_ty(), prop()))
94}
95/// `LimitingNormalCone : (List Real -> Prop) -> List Real -> (List Real -> Prop)`
96/// N_L(C; x) = Limsup_{y→x, y∈C} N̂(C; y).
97pub fn limiting_normal_cone_ty() -> Expr {
98    arrow(
99        fn_ty(vec_ty(), prop()),
100        arrow(vec_ty(), fn_ty(vec_ty(), prop())),
101    )
102}
103/// `SubdiffSumRule : Prop`
104/// Limiting subdiff of sum: ∂(f+g)(x) ⊆ ∂f(x) + ∂g(x) under SNEC condition.
105pub fn subdiff_sum_rule_ty() -> Expr {
106    prop()
107}
108/// `SubdiffChainRule : Prop`
109/// Chain rule for limiting subdifferential of composed mappings.
110pub fn subdiff_chain_rule_ty() -> Expr {
111    prop()
112}
113/// `FuzzySum : Prop`
114/// Fuzzy (approximate) sum rule for regular subdifferentials.
115pub fn fuzzy_sum_rule_ty() -> Expr {
116    prop()
117}
118/// `IsProxRegularSet : (List Real -> Prop) -> Real -> Prop`
119/// C is r-prox-regular: the projection onto C is single-valued on a tube of radius r.
120pub fn is_prox_regular_set_ty() -> Expr {
121    arrow(fn_ty(vec_ty(), prop()), arrow(real_ty(), prop()))
122}
123/// `IsProxRegularFunction : (List Real -> Real) -> Real -> Prop`
124/// f is r-prox-regular: its epigraph is r-prox-regular as a set.
125pub fn is_prox_regular_function_ty() -> Expr {
126    arrow(fn_ty(vec_ty(), real_ty()), arrow(real_ty(), prop()))
127}
128/// `ProxRegularityProjection : (List Real -> Prop) -> Real -> Prop`
129/// For r-prox-regular C, proj_C is single-valued on the open r-tube around C.
130pub fn prox_regularity_projection_ty() -> Expr {
131    arrow(fn_ty(vec_ty(), prop()), arrow(real_ty(), prop()))
132}
133/// `SubsmoothFunction : (List Real -> Real) -> Prop`
134/// A subsmooth function: regular subdifferential equals limiting subdifferential everywhere.
135pub fn subsmooth_function_ty() -> Expr {
136    arrow(fn_ty(vec_ty(), real_ty()), prop())
137}
138/// `UniformlyProxRegular : (List Real -> Prop) -> Real -> Prop`
139/// Uniformly r-prox-regular: same r works at every boundary point.
140pub fn uniformly_prox_regular_ty() -> Expr {
141    arrow(fn_ty(vec_ty(), prop()), arrow(real_ty(), prop()))
142}
143/// `EpiConverges : (Nat -> List Real -> Real) -> (List Real -> Real) -> Prop`
144/// f_n epi-converges to f: epi(f_n) → epi(f) in the set convergence sense.
145pub fn epi_converges_ty() -> Expr {
146    let rn_r = fn_ty(vec_ty(), real_ty());
147    let seq_fn = seq_ty(rn_r.clone());
148    arrow(seq_fn, arrow(rn_r, prop()))
149}
150/// `EpiLimInf : (Nat -> List Real -> Real) -> List Real -> Real`
151/// (e-liminf f_n)(x) = liminf_{y_n → x} liminf_n f_n(y_n).
152pub fn epi_liminf_ty() -> Expr {
153    let rn_r = fn_ty(vec_ty(), real_ty());
154    arrow(seq_ty(rn_r.clone()), rn_r)
155}
156/// `EpiLimSup : (Nat -> List Real -> Real) -> List Real -> Real`
157/// (e-limsup f_n)(x) = liminf_{y_n → x} limsup_n f_n(y_n).
158pub fn epi_limsup_ty() -> Expr {
159    let rn_r = fn_ty(vec_ty(), real_ty());
160    arrow(seq_ty(rn_r.clone()), rn_r)
161}
162/// `MoscoConverges : (Nat -> List Real -> Real) -> (List Real -> Real) -> Prop`
163/// Mosco convergence: combines strong epi and weak epi convergence conditions.
164pub fn mosco_converges_ty() -> Expr {
165    let rn_r = fn_ty(vec_ty(), real_ty());
166    arrow(seq_ty(rn_r.clone()), arrow(rn_r, prop()))
167}
168/// `EpiConvergenceImpliesMinConvergence : Prop`
169/// If f_n epi-converges to f then inf f_n → inf f and argmin_n converges.
170pub fn epi_conv_implies_min_ty() -> Expr {
171    prop()
172}
173/// `MoscoImpliesEpi : Prop`
174/// Mosco convergence implies epi-convergence (in reflexive Banach spaces).
175pub fn mosco_implies_epi_ty() -> Expr {
176    prop()
177}
178/// `GammaLimInf : (Nat -> List Real -> Real) -> List Real -> Real`
179/// Γ-liminf_n f_n(x) = sup_{U∋x} liminf_n inf_{y∈U} f_n(y).
180pub fn gamma_liminf_ty() -> Expr {
181    let rn_r = fn_ty(vec_ty(), real_ty());
182    arrow(seq_ty(rn_r.clone()), rn_r)
183}
184/// `GammaLimSup : (Nat -> List Real -> Real) -> List Real -> Real`
185/// Γ-limsup_n f_n(x) = sup_{U∋x} lim_n inf_{y∈U} f_n(y).
186pub fn gamma_limsup_ty() -> Expr {
187    let rn_r = fn_ty(vec_ty(), real_ty());
188    arrow(seq_ty(rn_r.clone()), rn_r)
189}
190/// `GammaConverges : (Nat -> List Real -> Real) -> (List Real -> Real) -> Prop`
191/// f_n Γ-converges to f iff Γ-liminf = Γ-limsup = f.
192pub fn gamma_converges_ty() -> Expr {
193    let rn_r = fn_ty(vec_ty(), real_ty());
194    arrow(seq_ty(rn_r.clone()), arrow(rn_r, prop()))
195}
196/// `GammaCompactness : Prop`
197/// Every sequence of equi-coercive functions has a Γ-convergent subsequence.
198pub fn gamma_compactness_ty() -> Expr {
199    prop()
200}
201/// `GammaConvergenceMinimisers : Prop`
202/// Γ-convergence ensures convergence of minimisers and minimum values.
203pub fn gamma_conv_minimisers_ty() -> Expr {
204    prop()
205}
206/// `GammaConvergenceStability : Prop`
207/// Γ-convergence is stable under addition of continuous perturbations.
208pub fn gamma_conv_stability_ty() -> Expr {
209    prop()
210}
211/// `EkelandVariationalPrinciple : (List Real -> Real) -> Prop`
212/// For every ε > 0 and near-minimiser x₀ of lsc proper f bounded below,
213/// ∃ xε: f(xε) ≤ f(x₀) and f(xε) ≤ f(x) + ε‖x-xε‖ for all x.
214pub fn ekeland_variational_principle_ty() -> Expr {
215    arrow(fn_ty(vec_ty(), real_ty()), prop())
216}
217/// `BorweinPreissPrinciple : (List Real -> Real) -> Prop`
218/// Smooth variational principle: perturbation by a smooth gauge function.
219pub fn borwein_preiss_principle_ty() -> Expr {
220    arrow(fn_ty(vec_ty(), real_ty()), prop())
221}
222/// `DensityOfMinimisers : (List Real -> Real) -> Prop`
223/// The set of strict minimisers is dense (consequence of variational principles).
224pub fn density_of_minimisers_ty() -> Expr {
225    arrow(fn_ty(vec_ty(), real_ty()), prop())
226}
227/// `StarShapedMinimisation : Prop`
228/// Variational principle for star-shaped constraint sets.
229pub fn star_shaped_minimisation_ty() -> Expr {
230    prop()
231}
232/// `TrustRegionPrinciple : Prop`
233/// Trust-region model for approximate variational principles.
234pub fn trust_region_principle_ty() -> Expr {
235    prop()
236}
237/// `PalaisSmaleCondition : (List Real -> Real) -> Prop`
238/// (PS): every sequence with bounded values and gradient → 0 has a convergent subsequence.
239pub fn palais_smale_condition_ty() -> Expr {
240    arrow(fn_ty(vec_ty(), real_ty()), prop())
241}
242/// `Cerami condition : (List Real -> Real) -> Prop`
243/// Cerami variant of (PS): (1 + ‖x_n‖)‖∇f(x_n)‖ → 0.
244pub fn cerami_condition_ty() -> Expr {
245    arrow(fn_ty(vec_ty(), real_ty()), prop())
246}
247/// `MountainPassTheorem : (List Real -> Real) -> Prop`
248/// If f satisfies (PS) and has a mountain-pass geometry, ∃ critical point.
249pub fn mountain_pass_theorem_ty() -> Expr {
250    arrow(fn_ty(vec_ty(), real_ty()), prop())
251}
252/// `SaddlePointTheorem : (List Real -> Real) -> Prop`
253/// Generalisation of mountain pass to saddle geometries.
254pub fn saddle_point_theorem_ty() -> Expr {
255    arrow(fn_ty(vec_ty(), real_ty()), prop())
256}
257/// `LinkingTheorem : (List Real -> Real) -> Prop`
258/// Abstract linking theorem yielding critical points.
259pub fn linking_theorem_ty() -> Expr {
260    arrow(fn_ty(vec_ty(), real_ty()), prop())
261}
262/// `MorseIndex : (List Real -> Real) -> List Real -> Nat`
263/// Index of a nondegenerate critical point (dimension of negative eigenspace of Hessian).
264pub fn morse_index_ty() -> Expr {
265    arrow(fn_ty(vec_ty(), real_ty()), fn_ty(vec_ty(), nat_ty()))
266}
267/// `MorseInequalities : (List Real -> Real) -> Prop`
268/// Morse inequalities relating critical points and Betti numbers.
269pub fn morse_inequalities_ty() -> Expr {
270    arrow(fn_ty(vec_ty(), real_ty()), prop())
271}
272/// `DeformationLemma : (List Real -> Real) -> Prop`
273/// Deformation retract of sub-level sets avoiding critical levels.
274pub fn deformation_lemma_ty() -> Expr {
275    arrow(fn_ty(vec_ty(), real_ty()), prop())
276}
277/// Build an [`Environment`] containing variational analysis axioms and theorems.
278pub fn build_variational_analysis_env() -> Environment {
279    let mut env = Environment::new();
280    let axioms: &[(&str, Expr)] = &[
281        ("RegularSubdifferential", regular_subdifferential_ty()),
282        ("LimitingSubdifferential", limiting_subdifferential_ty()),
283        ("ClarkeSubdifferential", clarke_subdifferential_ty()),
284        (
285            "ClarkeGeneralizedGradient",
286            clarke_generalized_gradient_ty(),
287        ),
288        ("MordukhovichCriterion", mordukhovich_criterion_ty()),
289        ("LimitingNormalCone", limiting_normal_cone_ty()),
290        ("SubdiffSumRule", subdiff_sum_rule_ty()),
291        ("SubdiffChainRule", subdiff_chain_rule_ty()),
292        ("FuzzySumRule", fuzzy_sum_rule_ty()),
293        ("IsProxRegularSet", is_prox_regular_set_ty()),
294        ("IsProxRegularFunction", is_prox_regular_function_ty()),
295        ("ProxRegularityProjection", prox_regularity_projection_ty()),
296        ("SubmootFunction", subsmooth_function_ty()),
297        ("UniformlyProxRegular", uniformly_prox_regular_ty()),
298        ("EpiConverges", epi_converges_ty()),
299        ("EpiLimInf", epi_liminf_ty()),
300        ("EpiLimSup", epi_limsup_ty()),
301        ("MoscoConverges", mosco_converges_ty()),
302        ("EpiConvImpliesMin", epi_conv_implies_min_ty()),
303        ("MoscoImpliesEpi", mosco_implies_epi_ty()),
304        ("GammaLimInf", gamma_liminf_ty()),
305        ("GammaLimSup", gamma_limsup_ty()),
306        ("GammaConverges", gamma_converges_ty()),
307        ("GammaCompactness", gamma_compactness_ty()),
308        ("GammaConvergenceMinimisers", gamma_conv_minimisers_ty()),
309        ("GammaConvergenceStability", gamma_conv_stability_ty()),
310        (
311            "EkelandVariationalPrinciple",
312            ekeland_variational_principle_ty(),
313        ),
314        ("BorweinPreissPrinciple", borwein_preiss_principle_ty()),
315        ("DensityOfMinimisers", density_of_minimisers_ty()),
316        ("StarShapedMinimisation", star_shaped_minimisation_ty()),
317        ("TrustRegionPrinciple", trust_region_principle_ty()),
318        ("PalaisSmaleCondition", palais_smale_condition_ty()),
319        ("CeramiCondition", cerami_condition_ty()),
320        ("MountainPassTheorem", mountain_pass_theorem_ty()),
321        ("SaddlePointTheorem", saddle_point_theorem_ty()),
322        ("LinkingTheorem", linking_theorem_ty()),
323        ("MorseIndex", morse_index_ty()),
324        ("MorseInequalities", morse_inequalities_ty()),
325        ("DeformationLemma", deformation_lemma_ty()),
326    ];
327    for &(name, ref ty) in axioms {
328        env.add(Declaration::Axiom {
329            name: Name::str(name),
330            univ_params: vec![],
331            ty: ty.clone(),
332        })
333        .ok();
334    }
335    env
336}
337/// Compute a Clarke-type subgradient via smoothing (convolution with Gaussian).
338/// For locally Lipschitz f, approximate Clarke gradient by averaging finite differences.
339pub fn clarke_gradient_approx(f: impl Fn(&[f64]) -> f64, x: &[f64], epsilon: f64) -> Vec<f64> {
340    let n = x.len();
341    let mut grad = vec![0.0; n];
342    let h = epsilon;
343    for i in 0..n {
344        let mut xp = x.to_vec();
345        xp[i] += h;
346        let mut xm = x.to_vec();
347        xm[i] -= h;
348        grad[i] = (f(&xp) - f(&xm)) / (2.0 * h);
349    }
350    grad
351}
352/// Check whether a vector is a regular (Fréchet) subgradient:
353/// liminf_{y→x} (f(y) - f(x) - ⟨v, y-x⟩) / ‖y-x‖ ≥ -tol
354/// by sampling random directions.
355pub fn check_regular_subgradient(
356    f: impl Fn(&[f64]) -> f64,
357    x: &[f64],
358    v: &[f64],
359    h: f64,
360    tol: f64,
361) -> bool {
362    let n = x.len();
363    let fx = f(x);
364    for i in 0..n {
365        for sign in &[-1.0_f64, 1.0_f64] {
366            let mut d = vec![0.0; n];
367            d[i] = *sign;
368            let y: Vec<f64> = x.iter().zip(d.iter()).map(|(xi, di)| xi + h * di).collect();
369            let fy = f(&y);
370            let inner: f64 = v.iter().zip(d.iter()).map(|(vi, di)| vi * di * h).sum();
371            let ratio = (fy - fx - inner) / h;
372            if ratio < -tol {
373                return false;
374            }
375        }
376    }
377    true
378}
379/// Approximate Ekeland minimiser: finds x_ε near x₀ such that
380/// f(x_ε) ≤ f(x₀) and f(x_ε) ≤ f(x) + eps * ‖x - x_ε‖ for all tested x.
381pub fn ekeland_approximate_minimiser(
382    f: impl Fn(&[f64]) -> f64,
383    x0: &[f64],
384    eps: f64,
385    max_iter: usize,
386) -> Vec<f64> {
387    let mut x = x0.to_vec();
388    let step = eps * 0.1;
389    let n = x.len();
390    let mut fx = f(&x);
391    for _ in 0..max_iter {
392        let mut improved = false;
393        for i in 0..n {
394            for &sign in &[-1.0_f64, 1.0_f64] {
395                let mut y = x.clone();
396                y[i] += sign * step;
397                let fy = f(&y);
398                let dist: f64 = x
399                    .iter()
400                    .zip(y.iter())
401                    .map(|(a, b)| (a - b).powi(2))
402                    .sum::<f64>()
403                    .sqrt();
404                if fy + eps * dist < fx - 1e-12 {
405                    fx = fy;
406                    x = y;
407                    improved = true;
408                }
409            }
410        }
411        if !improved {
412            break;
413        }
414    }
415    x
416}
417/// Check whether a finite sequence is a Palais-Smale sequence:
418/// |f(x_n)| bounded AND ‖∇f(x_n)‖ → 0.
419pub fn is_palais_smale_sequence(
420    f: impl Fn(&[f64]) -> f64,
421    grad_f: impl Fn(&[f64]) -> Vec<f64>,
422    sequence: &[Vec<f64>],
423    bound: f64,
424) -> bool {
425    if sequence.is_empty() {
426        return true;
427    }
428    for x in sequence {
429        if f(x).abs() > bound {
430            return false;
431        }
432    }
433    let last = sequence
434        .last()
435        .expect("sequence is non-empty: checked by early return");
436    let g = grad_f(last);
437    let norm: f64 = g.iter().map(|gi| gi * gi).sum::<f64>().sqrt();
438    norm < 0.1 * bound.max(1.0)
439}
440/// Compute penalty approximation of indicator function of [a, b]:
441/// f_n(x) = n * (max(0, x - b) + max(0, a - x)).
442pub fn penalty_indicator_1d(x: f64, a: f64, b: f64, n: f64) -> f64 {
443    n * (0.0_f64.max(x - b) + 0.0_f64.max(a - x))
444}
445/// Check whether a sequence of penalty functions Gamma-converges to the indicator of [a,b].
446/// Only verifies the liminf inequality at a test point.
447pub fn check_gamma_convergence_indicator(x: f64, a: f64, b: f64, ns: &[f64]) -> bool {
448    let inside = x >= a && x <= b;
449    if inside {
450        ns.iter()
451            .all(|&n| (penalty_indicator_1d(x, a, b, n) - 0.0).abs() < 1e-12)
452    } else {
453        let vals: Vec<f64> = ns
454            .iter()
455            .map(|&n| penalty_indicator_1d(x, a, b, n))
456            .collect();
457        vals.windows(2).all(|w| w[1] >= w[0] - 1e-12)
458    }
459}
460#[cfg(test)]
461mod tests {
462    use super::*;
463    #[test]
464    fn test_build_env_keys() {
465        let env = build_variational_analysis_env();
466        assert!(env.get(&Name::str("RegularSubdifferential")).is_some());
467        assert!(env.get(&Name::str("LimitingSubdifferential")).is_some());
468        assert!(env.get(&Name::str("ClarkeSubdifferential")).is_some());
469        assert!(env.get(&Name::str("EkelandVariationalPrinciple")).is_some());
470        assert!(env.get(&Name::str("MountainPassTheorem")).is_some());
471        assert!(env.get(&Name::str("PalaisSmaleCondition")).is_some());
472        assert!(env.get(&Name::str("GammaConverges")).is_some());
473        assert!(env.get(&Name::str("MoscoConverges")).is_some());
474    }
475    #[test]
476    fn test_regular_subgradient_smooth() {
477        let f = |x: &[f64]| x[0] * x[0];
478        let x = vec![1.0];
479        let v = vec![2.0];
480        assert!(check_regular_subgradient(f, &x, &v, 1e-4, 1e-5));
481    }
482    #[test]
483    fn test_clarke_gradient_smooth_quadratic() {
484        let f = |x: &[f64]| 0.5 * x[0] * x[0];
485        let x = vec![2.0];
486        let g = clarke_gradient_approx(f, &x, 1e-6);
487        assert!((g[0] - 2.0).abs() < 1e-4, "Clarke grad = {}", g[0]);
488    }
489    #[test]
490    fn test_epi_liminf_constant_sequence() {
491        let fns: Vec<Box<dyn Fn(&[f64]) -> f64 + Send + Sync>> = (0..5)
492            .map(|_| Box::new(|x: &[f64]| x[0] * x[0]) as Box<dyn Fn(&[f64]) -> f64 + Send + Sync>)
493            .collect();
494        let seq = FunctionSequence::new(fns);
495        let x = vec![0.0];
496        let val = seq.epi_liminf(&x, 0.5, 10);
497        assert!(val >= -1e-9, "epi-liminf at 0 = {val}");
498        assert!(val < 0.26, "epi-liminf at 0 too large: {val}");
499    }
500    #[test]
501    fn test_mountain_pass_geometry() {
502        let f = |x: &[f64]| x[0].powi(4) - 2.0 * x[0].powi(2);
503        let mut cfg = MountainPassConfig::new(vec![-1.0], vec![1.0], 100);
504        let pass_level = cfg.estimate_pass_level(&f);
505        assert!((pass_level - 0.0).abs() < 1e-9, "pass level = {pass_level}");
506        assert!(cfg.has_mountain_pass_geometry(&f));
507    }
508    #[test]
509    fn test_ekeland_approximate_minimiser() {
510        let f = |x: &[f64]| x[0] * x[0];
511        let x_eps = ekeland_approximate_minimiser(f, &[3.0], 1.0, 1000);
512        assert!(x_eps[0].abs() < 1.0, "Ekeland minimiser at {}", x_eps[0]);
513    }
514    #[test]
515    fn test_palais_smale_sequence() {
516        let f = |x: &[f64]| x[0] * x[0];
517        let grad_f = |x: &[f64]| vec![2.0 * x[0]];
518        let seq: Vec<Vec<f64>> = vec![vec![0.1], vec![0.01], vec![0.001]];
519        assert!(is_palais_smale_sequence(f, grad_f, &seq, 10.0));
520    }
521    #[test]
522    fn test_prox_regular_set() {
523        let pts: Vec<Vec<f64>> = (0..20)
524            .map(|k| {
525                let theta = k as f64 * std::f64::consts::TAU / 20.0;
526                vec![theta.cos(), theta.sin()]
527            })
528            .collect();
529        let prs = ProxRegularSet::new(pts, 0.5);
530        let x = vec![0.8, 0.0];
531        assert!(
532            prs.has_unique_projection(&x),
533            "near point should have unique projection"
534        );
535    }
536    #[test]
537    fn test_gamma_convergence_indicator() {
538        let ns = vec![1.0, 2.0, 5.0, 10.0, 50.0, 100.0];
539        assert!(check_gamma_convergence_indicator(0.5, 0.0, 1.0, &ns));
540        assert!(check_gamma_convergence_indicator(2.0, 0.0, 1.0, &ns));
541    }
542}
543/// Build an `Environment` with variational analysis kernel axioms.
544pub fn build_env() -> oxilean_kernel::Environment {
545    build_variational_analysis_env()
546}
547/// `TopologicalEpiConvergence : (Nat -> List Real -> Real) -> (List Real -> Real) -> Prop`
548/// Epi-convergence in a general topological space setting.
549pub fn topological_epi_convergence_ty() -> Expr {
550    let rn_r = fn_ty(vec_ty(), real_ty());
551    arrow(seq_ty(rn_r.clone()), arrow(rn_r, prop()))
552}
553/// `MoscoConvergenceReflexiveBanach : Prop`
554/// In reflexive Banach spaces, Mosco convergence is equivalent to weak epi + strong epi convergence.
555pub fn mosco_convergence_reflexive_banach_ty() -> Expr {
556    prop()
557}
558/// `EpiConvergenceStability : Prop`
559/// Epi-convergence is stable under continuous perturbations of the functionals.
560pub fn epi_convergence_stability_ty() -> Expr {
561    prop()
562}
563/// `MetricRegularity : (List Real -> List Real -> Prop) -> Real -> Prop`
564/// F is metrically regular at (x,y) with constant κ: d(x, F⁻¹(y)) ≤ κ d(y, F(x)).
565pub fn metric_regularity_ty() -> Expr {
566    arrow(
567        fn_ty(vec_ty(), fn_ty(vec_ty(), prop())),
568        arrow(real_ty(), prop()),
569    )
570}
571/// `LinearRegularity : (List (List Real -> Prop)) -> Prop`
572/// A collection of sets is linearly regular: d(x, ∩Cᵢ) ≤ κ max d(x, Cᵢ).
573pub fn linear_regularity_ty() -> Expr {
574    arrow(list_ty(fn_ty(vec_ty(), prop())), prop())
575}
576/// `LipschitzStability : (List Real -> List Real) -> Real -> Prop`
577/// A map F is Lipschitz-stable (calm) with constant κ near a point.
578pub fn lipschitz_stability_ty() -> Expr {
579    arrow(fn_ty(vec_ty(), vec_ty()), arrow(real_ty(), prop()))
580}
581/// `RobinsonStability : (List Real -> List Real -> Prop) -> Prop`
582/// Robinson's stability theorem: MFCQ implies Lipschitz stability of solution maps.
583pub fn robinson_stability_ty() -> Expr {
584    arrow(fn_ty(vec_ty(), fn_ty(vec_ty(), prop())), prop())
585}
586/// `AubinPropertyCriteria : (List Real -> List Real -> Prop) -> Prop`
587/// Aubin (pseudo-Lipschitz) property: set-valued map has Lipschitz-like continuity.
588pub fn aubin_property_criteria_ty() -> Expr {
589    arrow(fn_ty(vec_ty(), fn_ty(vec_ty(), prop())), prop())
590}
591/// `ClarkeSubdiffSumRule : Prop`
592/// Clarke's sum rule: ∂^C(f+g)(x) ⊆ ∂^C f(x) + ∂^C g(x).
593pub fn clarke_subdiff_sum_rule_ty() -> Expr {
594    prop()
595}
596/// `ClarkeSubdiffChainRule : Prop`
597/// Clarke's chain rule for composite locally Lipschitz functions.
598pub fn clarke_subdiff_chain_rule_ty() -> Expr {
599    prop()
600}
601/// `MordukhovichSubdiffSumRule : Prop`
602/// Mordukhovich sum rule with SNC (sequential normal compactness) condition.
603pub fn mordukhovich_subdiff_sum_rule_ty() -> Expr {
604    prop()
605}
606/// `MordukhovichSubdiffChainRule : Prop`
607/// Mordukhovich chain rule for compositions of Lipschitz mappings.
608pub fn mordukhovich_subdiff_chain_rule_ty() -> Expr {
609    prop()
610}
611/// `ClarkeStationaryCondition : (List Real -> Real) -> List Real -> Prop`
612/// 0 ∈ ∂^C f(x): Clarke stationarity as a necessary optimality condition.
613pub fn clarke_stationary_condition_ty() -> Expr {
614    arrow(fn_ty(vec_ty(), real_ty()), arrow(vec_ty(), prop()))
615}
616/// `EkelandWithErrorBound : (List Real -> Real) -> Real -> Prop`
617/// Ekeland's principle with explicit error bound for approximate minimisers.
618pub fn ekeland_with_error_bound_ty() -> Expr {
619    arrow(fn_ty(vec_ty(), real_ty()), arrow(real_ty(), prop()))
620}
621/// `BorweinPreissSmooth : (List Real -> Real) -> Prop`
622/// Borwein-Preiss: for any ε > 0, there exists a smooth perturbation realising a minimiser.
623pub fn borwein_preiss_smooth_ty() -> Expr {
624    arrow(fn_ty(vec_ty(), real_ty()), prop())
625}
626/// `FloweringPrinciple : Prop`
627/// Stegall's "flowering" variational principle in Asplund spaces.
628pub fn flowering_principle_ty() -> Expr {
629    prop()
630}
631/// `Deville_Godefroy_Zizler : Prop`
632/// Deville-Godefroy-Zizler variational principle: smooth bump function perturbation.
633pub fn deville_godefroy_zizler_ty() -> Expr {
634    prop()
635}
636/// `ProximalNormalCone : (List Real -> Prop) -> List Real -> (List Real -> Prop)`
637/// N̂^P(C;x): proximal normals via squared distance to C.
638pub fn proximal_normal_cone_ty() -> Expr {
639    arrow(
640        fn_ty(vec_ty(), prop()),
641        arrow(vec_ty(), fn_ty(vec_ty(), prop())),
642    )
643}
644/// `FrechetNormalCone : (List Real -> Prop) -> List Real -> (List Real -> Prop)`
645/// N̂(C;x): Fréchet (regular) normal cone — subdifferential of indicator function.
646pub fn frechet_normal_cone_ty() -> Expr {
647    arrow(
648        fn_ty(vec_ty(), prop()),
649        arrow(vec_ty(), fn_ty(vec_ty(), prop())),
650    )
651}
652/// `NormalConeInclusion : Prop`
653/// Proximal ⊆ Fréchet ⊆ Limiting normal cone (fundamental inclusions).
654pub fn normal_cone_inclusion_ty() -> Expr {
655    prop()
656}
657/// `NormalConeCalculus : (List Real -> Prop) -> (List Real -> Prop) -> Prop`
658/// Normal cone calculus: N(C₁ ∩ C₂; x) ⊆ N(C₁; x) + N(C₂; x) under regularity.
659pub fn normal_cone_calculus_ty() -> Expr {
660    arrow(
661        fn_ty(vec_ty(), prop()),
662        arrow(fn_ty(vec_ty(), prop()), prop()),
663    )
664}
665/// `MordukhovichCoderivative : (List Real -> List Real) -> List Real -> (List Real -> List Real -> Prop)`
666/// D*F(x)(v): coderivative of a set-valued map F at (x, y).
667pub fn mordukhovich_coderivative_ty() -> Expr {
668    arrow(
669        fn_ty(vec_ty(), vec_ty()),
670        arrow(vec_ty(), fn_ty(vec_ty(), fn_ty(vec_ty(), prop()))),
671    )
672}
673/// `StrictDifferentiability : (List Real -> List Real) -> List Real -> Prop`
674/// F is strictly differentiable at x: limit of (F(y)-F(z))/(y-z) as y,z→x is uniform.
675pub fn strict_differentiability_ty() -> Expr {
676    arrow(fn_ty(vec_ty(), vec_ty()), arrow(vec_ty(), prop()))
677}
678/// `ClarkeGeneralisedJacobian : (List Real -> List Real) -> List Real -> Type`
679/// ∂^C F(x) = conv{lim ∇F(xₙ) | xₙ → x, xₙ ∉ Ω_F}: Clarke generalised Jacobian.
680pub fn clarke_generalised_jacobian_ty() -> Expr {
681    arrow(fn_ty(vec_ty(), vec_ty()), arrow(vec_ty(), type0()))
682}
683/// `HausdorffContinuity : (List Real -> List Real -> Prop) -> Prop`
684/// A set-valued map is Hausdorff continuous if d_H(F(x), F(y)) → 0 as y → x.
685pub fn hausdorff_continuity_ty() -> Expr {
686    arrow(fn_ty(vec_ty(), fn_ty(vec_ty(), prop())), prop())
687}
688/// `BergeMaximumTheorem : (List Real -> Real) -> (List Real -> List Real -> Prop) -> Prop`
689/// Berge's maximum theorem: if f is continuous and C is continuous compact-valued,
690/// then V(x) = max_{y∈C(x)} f(x,y) is continuous and the argmax correspondence is USC.
691pub fn berge_maximum_theorem_ty() -> Expr {
692    arrow(
693        fn_ty(vec_ty(), real_ty()),
694        arrow(fn_ty(vec_ty(), fn_ty(vec_ty(), prop())), prop()),
695    )
696}
697/// `MichaelSelectionTheorem : (List Real -> List Real -> Prop) -> Prop`
698/// Michael's selection theorem: an LSC map with convex closed values has a continuous selection.
699pub fn michael_selection_theorem_ty() -> Expr {
700    arrow(fn_ty(vec_ty(), fn_ty(vec_ty(), prop())), prop())
701}
702/// `KuratowskiRyllNardzewski : (List Real -> List Real -> Prop) -> Prop`
703/// Kuratowski-Ryll-Nardzewski measurable selection theorem.
704pub fn kuratowski_ryll_nardzewski_ty() -> Expr {
705    arrow(fn_ty(vec_ty(), fn_ty(vec_ty(), prop())), prop())
706}
707/// `ProximalPointAlgorithm : (List Real -> Real) -> Prop`
708/// Proximal point algorithm: x_{k+1} = prox_{λf}(xₖ) converges to a minimiser.
709pub fn proximal_point_algorithm_ty() -> Expr {
710    arrow(fn_ty(vec_ty(), real_ty()), prop())
711}
712/// `BundleMethod : (List Real -> Real) -> Prop`
713/// Bundle method: collects subgradient information in a "bundle" to build cutting planes.
714pub fn bundle_method_ty() -> Expr {
715    arrow(fn_ty(vec_ty(), real_ty()), prop())
716}
717/// `ProxRegularMinimisation : (List Real -> Real) -> Real -> Prop`
718/// Prox-regularity ensures the proximal point algorithm converges at linear rate.
719pub fn prox_regular_minimisation_ty() -> Expr {
720    arrow(fn_ty(vec_ty(), real_ty()), arrow(real_ty(), prop()))
721}
722/// `DouglasRachfordSplitting : (List Real -> Real) -> (List Real -> Real) -> Prop`
723/// Douglas-Rachford splitting for minimising f + g via proximal operators.
724pub fn douglas_rachford_splitting_ty() -> Expr {
725    let rn_r = fn_ty(vec_ty(), real_ty());
726    arrow(rn_r.clone(), arrow(rn_r, prop()))
727}
728/// `UpperDiniDerivative : (List Real -> Real) -> List Real -> List Real -> Real`
729/// D⁺f(x;v) = limsup_{t↓0} (f(x+tv) - f(x))/t.
730pub fn upper_dini_derivative_ty() -> Expr {
731    arrow(
732        fn_ty(vec_ty(), real_ty()),
733        arrow(vec_ty(), arrow(vec_ty(), real_ty())),
734    )
735}
736/// `LowerDiniDerivative : (List Real -> Real) -> List Real -> List Real -> Real`
737/// D₋f(x;v) = liminf_{t↓0} (f(x+tv) - f(x))/t.
738pub fn lower_dini_derivative_ty() -> Expr {
739    arrow(
740        fn_ty(vec_ty(), real_ty()),
741        arrow(vec_ty(), arrow(vec_ty(), real_ty())),
742    )
743}
744/// `Quasidifferential : (List Real -> Real) -> List Real -> Type`
745/// Quasidifferential ∂f(x) = (Df(x), -Df(x)): pair of convex compact sets characterising df.
746pub fn quasidifferential_ty() -> Expr {
747    arrow(fn_ty(vec_ty(), real_ty()), arrow(vec_ty(), type0()))
748}
749/// `ClarkeGeneralisedDirectionalDerivative : (List Real -> Real) -> List Real -> List Real -> Real`
750/// f°(x;v) = limsup_{y→x, t↓0} (f(y+tv) - f(y))/t.
751pub fn clarke_directional_derivative_ty() -> Expr {
752    arrow(
753        fn_ty(vec_ty(), real_ty()),
754        arrow(vec_ty(), arrow(vec_ty(), real_ty())),
755    )
756}
757/// `GradientFlow : (List Real -> Real) -> Prop`
758/// Gradient flow: ẋ ∈ -∂f(x), the steepest descent curve in the sense of maximal slope.
759pub fn gradient_flow_ty() -> Expr {
760    arrow(fn_ty(vec_ty(), real_ty()), prop())
761}
762/// `BregmanDynamics : (List Real -> Real) -> (List Real -> Real) -> Prop`
763/// Bregman proximal dynamics: xₖ₊₁ = argmin {f(x) + Dφ(x, xₖ)/λ}.
764pub fn bregman_dynamics_ty() -> Expr {
765    let rn_r = fn_ty(vec_ty(), real_ty());
766    arrow(rn_r.clone(), arrow(rn_r, prop()))
767}
768/// `SaddlePointDynamics : (List Real -> List Real -> Real) -> Prop`
769/// Primal-dual saddle point dynamics: (ẋ, ẏ) = (-∂_x L(x,y), ∂_y L(x,y)).
770pub fn saddle_point_dynamics_ty() -> Expr {
771    arrow(fn_ty(vec_ty(), fn_ty(vec_ty(), real_ty())), prop())
772}
773/// `MirrorDescentAlgorithm : (List Real -> Real) -> (List Real -> Real) -> Prop`
774/// Mirror descent: generalises gradient descent to curved geometries via Bregman divergence.
775pub fn mirror_descent_algorithm_ty() -> Expr {
776    let rn_r = fn_ty(vec_ty(), real_ty());
777    arrow(rn_r.clone(), arrow(rn_r, prop()))
778}
779/// `IsQuasiconvex : (List Real -> Real) -> Prop`
780/// f is quasiconvex: level sets {x | f(x) ≤ α} are convex for all α.
781pub fn is_quasiconvex_ty() -> Expr {
782    arrow(fn_ty(vec_ty(), real_ty()), prop())
783}
784/// `IsPseudoconvex : (List Real -> Real) -> Prop`
785/// f is pseudoconvex: ⟨∇f(x), y-x⟩ ≥ 0 ⟹ f(y) ≥ f(x).
786pub fn is_pseudoconvex_ty() -> Expr {
787    arrow(fn_ty(vec_ty(), real_ty()), prop())
788}
789/// `QuasiconvexLevelSets : (List Real -> Real) -> Real -> Prop`
790/// Level set {x | f(x) ≤ α} is convex for each α ∈ ℝ when f is quasiconvex.
791pub fn quasiconvex_level_sets_ty() -> Expr {
792    arrow(fn_ty(vec_ty(), real_ty()), arrow(real_ty(), prop()))
793}
794/// `StrictQuasiconvexity : (List Real -> Real) -> Prop`
795/// Strict quasiconvexity: f(λx + (1-λ)y) < max(f(x), f(y)) for x≠y, λ∈(0,1).
796pub fn strict_quasiconvexity_ty() -> Expr {
797    arrow(fn_ty(vec_ty(), real_ty()), prop())
798}
799/// `MFCQ : (List (List Real -> Real)) -> (List (List Real -> Real)) -> List Real -> Prop`
800/// Mangasarian-Fromovitz CQ: active constraint gradients + inequality multipliers positively independent.
801pub fn mfcq_ty() -> Expr {
802    let rn_r = fn_ty(vec_ty(), real_ty());
803    let constraint_list = list_ty(rn_r.clone());
804    arrow(
805        constraint_list.clone(),
806        arrow(constraint_list, arrow(vec_ty(), prop())),
807    )
808}
809/// `LICQ : (List (List Real -> Real)) -> List Real -> Prop`
810/// Linear Independence CQ: gradients of active constraints are linearly independent.
811pub fn licq_ty() -> Expr {
812    let rn_r = fn_ty(vec_ty(), real_ty());
813    arrow(list_ty(rn_r), arrow(vec_ty(), prop()))
814}
815/// `SecondOrderSufficientConditions : (List Real -> Real) -> List Real -> Prop`
816/// SOSC: positive definiteness of Lagrangian Hessian on the critical cone.
817pub fn second_order_sufficient_conditions_ty() -> Expr {
818    arrow(fn_ty(vec_ty(), real_ty()), arrow(vec_ty(), prop()))
819}
820/// `SecondOrderNecessaryConditions : (List Real -> Real) -> List Real -> Prop`
821/// SONC: Hessian of Lagrangian is PSD on the critical cone at a local minimiser.
822pub fn second_order_necessary_conditions_ty() -> Expr {
823    arrow(fn_ty(vec_ty(), real_ty()), arrow(vec_ty(), prop()))
824}
825/// `KKTConditions : (List Real -> Real) -> (List (List Real -> Real)) -> List Real -> Prop`
826/// Karush-Kuhn-Tucker necessary optimality conditions.
827pub fn kkt_conditions_ty() -> Expr {
828    let rn_r = fn_ty(vec_ty(), real_ty());
829    arrow(rn_r.clone(), arrow(list_ty(rn_r), arrow(vec_ty(), prop())))
830}
831/// Build the extended variational analysis environment (adds §7 axioms to existing §1-§6).
832pub fn build_variational_analysis_env_extended() -> Environment {
833    let mut env = build_variational_analysis_env();
834    let axioms: &[(&str, Expr)] = &[
835        (
836            "TopologicalEpiConvergence",
837            topological_epi_convergence_ty(),
838        ),
839        (
840            "MoscoConvergenceReflexiveBanach",
841            mosco_convergence_reflexive_banach_ty(),
842        ),
843        ("EpiConvergenceStability", epi_convergence_stability_ty()),
844        ("MetricRegularity", metric_regularity_ty()),
845        ("LinearRegularity", linear_regularity_ty()),
846        ("LipschitzStability", lipschitz_stability_ty()),
847        ("RobinsonStability", robinson_stability_ty()),
848        ("AubinPropertyCriteria", aubin_property_criteria_ty()),
849        ("ClarkeSubdiffSumRule", clarke_subdiff_sum_rule_ty()),
850        ("ClarkeSubdiffChainRule", clarke_subdiff_chain_rule_ty()),
851        (
852            "MordukhovichSubdiffSumRule",
853            mordukhovich_subdiff_sum_rule_ty(),
854        ),
855        (
856            "MordukhovichSubdiffChainRule",
857            mordukhovich_subdiff_chain_rule_ty(),
858        ),
859        (
860            "ClarkeStationaryCondition",
861            clarke_stationary_condition_ty(),
862        ),
863        ("EkelandWithErrorBound", ekeland_with_error_bound_ty()),
864        ("BorweinPreissSmooth", borwein_preiss_smooth_ty()),
865        ("FloweringPrinciple", flowering_principle_ty()),
866        ("DevilleGodefroyZizler", deville_godefroy_zizler_ty()),
867        ("ProximalNormalCone", proximal_normal_cone_ty()),
868        ("FrechetNormalCone", frechet_normal_cone_ty()),
869        ("NormalConeInclusion", normal_cone_inclusion_ty()),
870        ("NormalConeCalculus", normal_cone_calculus_ty()),
871        ("MordukhovichCoderivative", mordukhovich_coderivative_ty()),
872        ("StrictDifferentiability", strict_differentiability_ty()),
873        (
874            "ClarkeGeneralisedJacobian",
875            clarke_generalised_jacobian_ty(),
876        ),
877        ("HausdorffContinuity", hausdorff_continuity_ty()),
878        ("BergeMaximumTheorem", berge_maximum_theorem_ty()),
879        ("MichaelSelectionTheorem", michael_selection_theorem_ty()),
880        ("KuratowskiRyllNardzewski", kuratowski_ryll_nardzewski_ty()),
881        ("ProximalPointAlgorithm", proximal_point_algorithm_ty()),
882        ("BundleMethod", bundle_method_ty()),
883        ("ProxRegularMinimisation", prox_regular_minimisation_ty()),
884        ("DouglasRachfordSplitting", douglas_rachford_splitting_ty()),
885        ("UpperDiniDerivative", upper_dini_derivative_ty()),
886        ("LowerDiniDerivative", lower_dini_derivative_ty()),
887        ("Quasidifferential", quasidifferential_ty()),
888        (
889            "ClarkeDirectionalDerivative",
890            clarke_directional_derivative_ty(),
891        ),
892        ("GradientFlow", gradient_flow_ty()),
893        ("BregmanDynamics", bregman_dynamics_ty()),
894        ("SaddlePointDynamics", saddle_point_dynamics_ty()),
895        ("MirrorDescentAlgorithm", mirror_descent_algorithm_ty()),
896        ("IsQuasiconvex", is_quasiconvex_ty()),
897        ("IsPseudoconvex", is_pseudoconvex_ty()),
898        ("QuasiconvexLevelSets", quasiconvex_level_sets_ty()),
899        ("StrictQuasiconvexity", strict_quasiconvexity_ty()),
900        ("MFCQ", mfcq_ty()),
901        ("LICQ", licq_ty()),
902        (
903            "SecondOrderSufficientConditions",
904            second_order_sufficient_conditions_ty(),
905        ),
906        (
907            "SecondOrderNecessaryConditions",
908            second_order_necessary_conditions_ty(),
909        ),
910        ("KKTConditions", kkt_conditions_ty()),
911    ];
912    for &(name, ref ty) in axioms {
913        env.add(Declaration::Axiom {
914            name: Name::str(name),
915            univ_params: vec![],
916            ty: ty.clone(),
917        })
918        .ok();
919    }
920    env
921}
922#[cfg(test)]
923mod tests_extended {
924    use super::*;
925    #[test]
926    fn test_extended_env_axioms_registered() {
927        let env = build_variational_analysis_env_extended();
928        assert!(env.get(&Name::str("MetricRegularity")).is_some());
929        assert!(env.get(&Name::str("LinearRegularity")).is_some());
930        assert!(env.get(&Name::str("MichaelSelectionTheorem")).is_some());
931        assert!(env.get(&Name::str("BergeMaximumTheorem")).is_some());
932        assert!(env.get(&Name::str("ProximalPointAlgorithm")).is_some());
933        assert!(env.get(&Name::str("DouglasRachfordSplitting")).is_some());
934        assert!(env.get(&Name::str("IsQuasiconvex")).is_some());
935        assert!(env.get(&Name::str("KKTConditions")).is_some());
936        assert!(env.get(&Name::str("MFCQ")).is_some());
937        assert!(env.get(&Name::str("LICQ")).is_some());
938        assert!(env.get(&Name::str("GradientFlow")).is_some());
939        assert!(env.get(&Name::str("BregmanDynamics")).is_some());
940    }
941    #[test]
942    fn test_ekeland_principle_smooth_quadratic() {
943        let f = |x: &[f64]| x[0] * x[0];
944        let ep = EkelandPrinciple::new(0.5, 500);
945        let x_eps = ep.find_minimiser(f, &[2.0]);
946        assert!(x_eps[0].abs() < 2.0, "Ekeland minimiser: {}", x_eps[0]);
947        let samples: Vec<Vec<f64>> = (0..5).map(|i| vec![i as f64 * 0.5]).collect();
948        assert!(ep.verify_ekeland_condition(f, &x_eps, &samples));
949    }
950    #[test]
951    fn test_mordukhovich_subdiff_stationary() {
952        let f = |x: &[f64]| x[0] * x[0];
953        let approx = MordukhovichSubdiffApprox::new(1e-5, 10, 1e-3);
954        assert!(approx.is_stationary(f, &[0.0]));
955        assert!(!approx.is_stationary(f, &[1.0]));
956    }
957    #[test]
958    fn test_mordukhovich_subdiff_frechet_quadratic() {
959        let f = |x: &[f64]| 0.5 * x[0] * x[0];
960        let approx = MordukhovichSubdiffApprox::new(1e-5, 5, 0.01);
961        let g = approx.frechet_subgradient(f, &[3.0]);
962        assert!(
963            (g[0] - 3.0).abs() < 1e-3,
964            "gradient at 3 should be ≈3, got {}",
965            g[0]
966        );
967    }
968    #[test]
969    fn test_proximal_point_solver_convergence() {
970        let f = |x: &[f64]| x[0] * x[0];
971        let solver = ProximalPointSolver::new(0.5, 50, 1e-4);
972        let iterates = solver.solve(f, &[3.0]);
973        assert!(!iterates.is_empty());
974        let last = iterates.last().expect("last should succeed");
975        assert!(
976            last[0].abs() < 3.0,
977            "PPA should move towards 0, got {}",
978            last[0]
979        );
980    }
981    #[test]
982    fn test_proximal_point_solver_converged() {
983        let f = |x: &[f64]| x[0] * x[0];
984        let solver = ProximalPointSolver::new(1.0, 200, 1e-3);
985        let iterates = solver.solve(f, &[1.0]);
986        assert!(
987            solver.has_converged(&iterates),
988            "PPA should converge for convex quadratic"
989        );
990    }
991    #[test]
992    fn test_metric_regularity_checker_quasiconvex() {
993        let f = |x: &[f64]| x[0] * x[0];
994        let result = MetricRegularityChecker::check_quasiconvex(f, &[-1.0], &[1.0], 20);
995        assert!(result, "x^2 should be quasiconvex");
996    }
997    #[test]
998    fn test_metric_regularity_checker_not_quasiconvex() {
999        let f = |x: &[f64]| -(x[0] * x[0]);
1000        let result = MetricRegularityChecker::check_quasiconvex(f, &[-1.0], &[1.0], 20);
1001        assert!(!result, "-(x^2) should NOT be quasiconvex");
1002    }
1003    #[test]
1004    fn test_metric_regularity_checker_cq() {
1005        let constraints: Vec<Box<dyn Fn(&[f64]) -> f64>> = vec![Box::new(|x: &[f64]| x[0])];
1006        let x = vec![0.0];
1007        let result =
1008            MetricRegularityChecker::check_constraint_qualification(&constraints, &x, 1e-5);
1009        assert!(result, "Single nonzero-gradient constraint satisfies CQ");
1010    }
1011    #[test]
1012    fn test_ekeland_near_minimality_gap() {
1013        let f = |x: &[f64]| (x[0] - 2.0).powi(2);
1014        let ep = EkelandPrinciple::new(0.1, 1000);
1015        let x_eps = ep.find_minimiser(f, &[5.0]);
1016        let gap = ep.near_minimality_gap(f, &x_eps, &[5.0]);
1017        assert!(gap >= 0.0, "gap should be non-negative: {gap}");
1018    }
1019    #[test]
1020    fn test_build_variational_env_original_still_present() {
1021        let env = build_variational_analysis_env_extended();
1022        assert!(env.get(&Name::str("RegularSubdifferential")).is_some());
1023        assert!(env.get(&Name::str("EkelandVariationalPrinciple")).is_some());
1024        assert!(env.get(&Name::str("MountainPassTheorem")).is_some());
1025        assert!(env.get(&Name::str("GammaConverges")).is_some());
1026    }
1027}
1028#[cfg(test)]
1029mod tests_variational_extended {
1030    use super::*;
1031    #[test]
1032    fn test_prox_l1_soft_threshold() {
1033        let prox = ProximalOperator::new(0.5, ProxFnType::L1Norm);
1034        assert!((prox.apply_scalar(1.5) - 1.0).abs() < 1e-10);
1035        assert!((prox.apply_scalar(0.3) - 0.0).abs() < 1e-10);
1036    }
1037    #[test]
1038    fn test_prox_l2_shrinkage() {
1039        let prox = ProximalOperator::new(1.0, ProxFnType::L2NormSquared);
1040        assert!((prox.apply_scalar(3.0) - 1.0).abs() < 1e-10);
1041    }
1042    #[test]
1043    fn test_prox_nonneg() {
1044        let prox = ProximalOperator::new(1.0, ProxFnType::NonNegativeOrtHant);
1045        assert!((prox.apply_scalar(-3.0) - 0.0).abs() < 1e-10);
1046        assert!((prox.apply_scalar(2.0) - 2.0).abs() < 1e-10);
1047    }
1048    #[test]
1049    fn test_moreau_decomposition() {
1050        let prox = ProximalOperator::new(1.0, ProxFnType::L1Norm);
1051        let v = 2.5;
1052        let (p, d) = prox.moreau_decomposition(v);
1053        assert!((p + d - v).abs() < 1e-10);
1054    }
1055    #[test]
1056    fn test_admm_dual_update() {
1057        let admm = ADMMSolver::new(1.0, 100, 1e-6);
1058        let y_new = admm.dual_update(0.5, 0.1);
1059        assert!((y_new - 0.6).abs() < 1e-10);
1060    }
1061    #[test]
1062    fn test_admm_stopping() {
1063        let admm = ADMMSolver::new(1.0, 100, 1e-6);
1064        assert!(admm.stopping_criteria(1e-7, 1e-7));
1065        assert!(!admm.stopping_criteria(1e-5, 1e-7));
1066    }
1067    #[test]
1068    fn test_gradient_descent_rate() {
1069        let f = ConvexSubdifferential::new("quadratic")
1070            .with_differentiability()
1071            .with_strong_convexity(1.0)
1072            .with_lipschitz(10.0);
1073        let rate = f
1074            .gradient_descent_rate()
1075            .expect("gradient_descent_rate should succeed");
1076        assert!((rate - 0.9).abs() < 1e-10);
1077    }
1078}