1use 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}
61pub 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}
69pub 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}
77pub 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}
85pub fn clarke_generalized_gradient_ty() -> Expr {
88 arrow(fn_ty(vec_ty(), real_ty()), fn_ty(vec_ty(), vec_ty()))
89}
90pub fn mordukhovich_criterion_ty() -> Expr {
93 arrow(fn_ty(vec_ty(), prop()), arrow(vec_ty(), prop()))
94}
95pub 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}
103pub fn subdiff_sum_rule_ty() -> Expr {
106 prop()
107}
108pub fn subdiff_chain_rule_ty() -> Expr {
111 prop()
112}
113pub fn fuzzy_sum_rule_ty() -> Expr {
116 prop()
117}
118pub fn is_prox_regular_set_ty() -> Expr {
121 arrow(fn_ty(vec_ty(), prop()), arrow(real_ty(), prop()))
122}
123pub fn is_prox_regular_function_ty() -> Expr {
126 arrow(fn_ty(vec_ty(), real_ty()), arrow(real_ty(), prop()))
127}
128pub fn prox_regularity_projection_ty() -> Expr {
131 arrow(fn_ty(vec_ty(), prop()), arrow(real_ty(), prop()))
132}
133pub fn subsmooth_function_ty() -> Expr {
136 arrow(fn_ty(vec_ty(), real_ty()), prop())
137}
138pub fn uniformly_prox_regular_ty() -> Expr {
141 arrow(fn_ty(vec_ty(), prop()), arrow(real_ty(), prop()))
142}
143pub 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}
150pub 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}
156pub 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}
162pub 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}
168pub fn epi_conv_implies_min_ty() -> Expr {
171 prop()
172}
173pub fn mosco_implies_epi_ty() -> Expr {
176 prop()
177}
178pub 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}
184pub 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}
190pub 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}
196pub fn gamma_compactness_ty() -> Expr {
199 prop()
200}
201pub fn gamma_conv_minimisers_ty() -> Expr {
204 prop()
205}
206pub fn gamma_conv_stability_ty() -> Expr {
209 prop()
210}
211pub fn ekeland_variational_principle_ty() -> Expr {
215 arrow(fn_ty(vec_ty(), real_ty()), prop())
216}
217pub fn borwein_preiss_principle_ty() -> Expr {
220 arrow(fn_ty(vec_ty(), real_ty()), prop())
221}
222pub fn density_of_minimisers_ty() -> Expr {
225 arrow(fn_ty(vec_ty(), real_ty()), prop())
226}
227pub fn star_shaped_minimisation_ty() -> Expr {
230 prop()
231}
232pub fn trust_region_principle_ty() -> Expr {
235 prop()
236}
237pub fn palais_smale_condition_ty() -> Expr {
240 arrow(fn_ty(vec_ty(), real_ty()), prop())
241}
242pub fn cerami_condition_ty() -> Expr {
245 arrow(fn_ty(vec_ty(), real_ty()), prop())
246}
247pub fn mountain_pass_theorem_ty() -> Expr {
250 arrow(fn_ty(vec_ty(), real_ty()), prop())
251}
252pub fn saddle_point_theorem_ty() -> Expr {
255 arrow(fn_ty(vec_ty(), real_ty()), prop())
256}
257pub fn linking_theorem_ty() -> Expr {
260 arrow(fn_ty(vec_ty(), real_ty()), prop())
261}
262pub fn morse_index_ty() -> Expr {
265 arrow(fn_ty(vec_ty(), real_ty()), fn_ty(vec_ty(), nat_ty()))
266}
267pub fn morse_inequalities_ty() -> Expr {
270 arrow(fn_ty(vec_ty(), real_ty()), prop())
271}
272pub fn deformation_lemma_ty() -> Expr {
275 arrow(fn_ty(vec_ty(), real_ty()), prop())
276}
277pub 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}
337pub 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}
352pub 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}
379pub 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}
417pub 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}
440pub 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}
445pub 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}
543pub fn build_env() -> oxilean_kernel::Environment {
545 build_variational_analysis_env()
546}
547pub 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}
553pub fn mosco_convergence_reflexive_banach_ty() -> Expr {
556 prop()
557}
558pub fn epi_convergence_stability_ty() -> Expr {
561 prop()
562}
563pub fn metric_regularity_ty() -> Expr {
566 arrow(
567 fn_ty(vec_ty(), fn_ty(vec_ty(), prop())),
568 arrow(real_ty(), prop()),
569 )
570}
571pub fn linear_regularity_ty() -> Expr {
574 arrow(list_ty(fn_ty(vec_ty(), prop())), prop())
575}
576pub fn lipschitz_stability_ty() -> Expr {
579 arrow(fn_ty(vec_ty(), vec_ty()), arrow(real_ty(), prop()))
580}
581pub fn robinson_stability_ty() -> Expr {
584 arrow(fn_ty(vec_ty(), fn_ty(vec_ty(), prop())), prop())
585}
586pub fn aubin_property_criteria_ty() -> Expr {
589 arrow(fn_ty(vec_ty(), fn_ty(vec_ty(), prop())), prop())
590}
591pub fn clarke_subdiff_sum_rule_ty() -> Expr {
594 prop()
595}
596pub fn clarke_subdiff_chain_rule_ty() -> Expr {
599 prop()
600}
601pub fn mordukhovich_subdiff_sum_rule_ty() -> Expr {
604 prop()
605}
606pub fn mordukhovich_subdiff_chain_rule_ty() -> Expr {
609 prop()
610}
611pub fn clarke_stationary_condition_ty() -> Expr {
614 arrow(fn_ty(vec_ty(), real_ty()), arrow(vec_ty(), prop()))
615}
616pub fn ekeland_with_error_bound_ty() -> Expr {
619 arrow(fn_ty(vec_ty(), real_ty()), arrow(real_ty(), prop()))
620}
621pub fn borwein_preiss_smooth_ty() -> Expr {
624 arrow(fn_ty(vec_ty(), real_ty()), prop())
625}
626pub fn flowering_principle_ty() -> Expr {
629 prop()
630}
631pub fn deville_godefroy_zizler_ty() -> Expr {
634 prop()
635}
636pub 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}
644pub 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}
652pub fn normal_cone_inclusion_ty() -> Expr {
655 prop()
656}
657pub fn normal_cone_calculus_ty() -> Expr {
660 arrow(
661 fn_ty(vec_ty(), prop()),
662 arrow(fn_ty(vec_ty(), prop()), prop()),
663 )
664}
665pub 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}
673pub fn strict_differentiability_ty() -> Expr {
676 arrow(fn_ty(vec_ty(), vec_ty()), arrow(vec_ty(), prop()))
677}
678pub fn clarke_generalised_jacobian_ty() -> Expr {
681 arrow(fn_ty(vec_ty(), vec_ty()), arrow(vec_ty(), type0()))
682}
683pub fn hausdorff_continuity_ty() -> Expr {
686 arrow(fn_ty(vec_ty(), fn_ty(vec_ty(), prop())), prop())
687}
688pub 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}
697pub fn michael_selection_theorem_ty() -> Expr {
700 arrow(fn_ty(vec_ty(), fn_ty(vec_ty(), prop())), prop())
701}
702pub fn kuratowski_ryll_nardzewski_ty() -> Expr {
705 arrow(fn_ty(vec_ty(), fn_ty(vec_ty(), prop())), prop())
706}
707pub fn proximal_point_algorithm_ty() -> Expr {
710 arrow(fn_ty(vec_ty(), real_ty()), prop())
711}
712pub fn bundle_method_ty() -> Expr {
715 arrow(fn_ty(vec_ty(), real_ty()), prop())
716}
717pub fn prox_regular_minimisation_ty() -> Expr {
720 arrow(fn_ty(vec_ty(), real_ty()), arrow(real_ty(), prop()))
721}
722pub 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}
728pub 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}
736pub 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}
744pub fn quasidifferential_ty() -> Expr {
747 arrow(fn_ty(vec_ty(), real_ty()), arrow(vec_ty(), type0()))
748}
749pub 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}
757pub fn gradient_flow_ty() -> Expr {
760 arrow(fn_ty(vec_ty(), real_ty()), prop())
761}
762pub 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}
768pub fn saddle_point_dynamics_ty() -> Expr {
771 arrow(fn_ty(vec_ty(), fn_ty(vec_ty(), real_ty())), prop())
772}
773pub 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}
779pub fn is_quasiconvex_ty() -> Expr {
782 arrow(fn_ty(vec_ty(), real_ty()), prop())
783}
784pub fn is_pseudoconvex_ty() -> Expr {
787 arrow(fn_ty(vec_ty(), real_ty()), prop())
788}
789pub fn quasiconvex_level_sets_ty() -> Expr {
792 arrow(fn_ty(vec_ty(), real_ty()), arrow(real_ty(), prop()))
793}
794pub fn strict_quasiconvexity_ty() -> Expr {
797 arrow(fn_ty(vec_ty(), real_ty()), prop())
798}
799pub 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}
809pub 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}
815pub fn second_order_sufficient_conditions_ty() -> Expr {
818 arrow(fn_ty(vec_ty(), real_ty()), arrow(vec_ty(), prop()))
819}
820pub fn second_order_necessary_conditions_ty() -> Expr {
823 arrow(fn_ty(vec_ty(), real_ty()), arrow(vec_ty(), prop()))
824}
825pub 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}
831pub 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}