use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
use super::types::{
ADMMSolver, ConvexSubdifferential, EkelandPrinciple, FunctionSequence, MetricRegularityChecker,
MordukhovichSubdiffApprox, MountainPassConfig, ProxFnType, ProxRegularSet, ProximalOperator,
ProximalPointSolver,
};
pub fn app(f: Expr, a: Expr) -> Expr {
Expr::App(Box::new(f), Box::new(a))
}
pub fn app2(f: Expr, a: Expr, b: Expr) -> Expr {
app(app(f, a), b)
}
pub fn app3(f: Expr, a: Expr, b: Expr, c: Expr) -> Expr {
app(app2(f, a, b), c)
}
pub fn cst(s: &str) -> Expr {
Expr::Const(Name::str(s), vec![])
}
pub fn prop() -> Expr {
Expr::Sort(Level::zero())
}
pub fn type0() -> Expr {
Expr::Sort(Level::succ(Level::zero()))
}
pub fn pi(bi: BinderInfo, name: &str, dom: Expr, body: Expr) -> Expr {
Expr::Pi(bi, Name::str(name), Box::new(dom), Box::new(body))
}
pub fn arrow(a: Expr, b: Expr) -> Expr {
pi(BinderInfo::Default, "_", a, b)
}
pub fn bvar(n: u32) -> Expr {
Expr::BVar(n)
}
pub fn real_ty() -> Expr {
cst("Real")
}
pub fn nat_ty() -> Expr {
cst("Nat")
}
pub fn list_ty(elem: Expr) -> Expr {
app(cst("List"), elem)
}
pub fn bool_ty() -> Expr {
cst("Bool")
}
pub fn fn_ty(dom: Expr, cod: Expr) -> Expr {
arrow(dom, cod)
}
pub fn vec_ty() -> Expr {
list_ty(real_ty())
}
pub fn seq_ty(elem: Expr) -> Expr {
fn_ty(nat_ty(), elem)
}
pub fn regular_subdifferential_ty() -> Expr {
arrow(
fn_ty(vec_ty(), real_ty()),
arrow(vec_ty(), fn_ty(vec_ty(), prop())),
)
}
pub fn limiting_subdifferential_ty() -> Expr {
arrow(
fn_ty(vec_ty(), real_ty()),
arrow(vec_ty(), fn_ty(vec_ty(), prop())),
)
}
pub fn clarke_subdifferential_ty() -> Expr {
arrow(
fn_ty(vec_ty(), real_ty()),
arrow(vec_ty(), fn_ty(vec_ty(), prop())),
)
}
pub fn clarke_generalized_gradient_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), fn_ty(vec_ty(), vec_ty()))
}
pub fn mordukhovich_criterion_ty() -> Expr {
arrow(fn_ty(vec_ty(), prop()), arrow(vec_ty(), prop()))
}
pub fn limiting_normal_cone_ty() -> Expr {
arrow(
fn_ty(vec_ty(), prop()),
arrow(vec_ty(), fn_ty(vec_ty(), prop())),
)
}
pub fn subdiff_sum_rule_ty() -> Expr {
prop()
}
pub fn subdiff_chain_rule_ty() -> Expr {
prop()
}
pub fn fuzzy_sum_rule_ty() -> Expr {
prop()
}
pub fn is_prox_regular_set_ty() -> Expr {
arrow(fn_ty(vec_ty(), prop()), arrow(real_ty(), prop()))
}
pub fn is_prox_regular_function_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), arrow(real_ty(), prop()))
}
pub fn prox_regularity_projection_ty() -> Expr {
arrow(fn_ty(vec_ty(), prop()), arrow(real_ty(), prop()))
}
pub fn subsmooth_function_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), prop())
}
pub fn uniformly_prox_regular_ty() -> Expr {
arrow(fn_ty(vec_ty(), prop()), arrow(real_ty(), prop()))
}
pub fn epi_converges_ty() -> Expr {
let rn_r = fn_ty(vec_ty(), real_ty());
let seq_fn = seq_ty(rn_r.clone());
arrow(seq_fn, arrow(rn_r, prop()))
}
pub fn epi_liminf_ty() -> Expr {
let rn_r = fn_ty(vec_ty(), real_ty());
arrow(seq_ty(rn_r.clone()), rn_r)
}
pub fn epi_limsup_ty() -> Expr {
let rn_r = fn_ty(vec_ty(), real_ty());
arrow(seq_ty(rn_r.clone()), rn_r)
}
pub fn mosco_converges_ty() -> Expr {
let rn_r = fn_ty(vec_ty(), real_ty());
arrow(seq_ty(rn_r.clone()), arrow(rn_r, prop()))
}
pub fn epi_conv_implies_min_ty() -> Expr {
prop()
}
pub fn mosco_implies_epi_ty() -> Expr {
prop()
}
pub fn gamma_liminf_ty() -> Expr {
let rn_r = fn_ty(vec_ty(), real_ty());
arrow(seq_ty(rn_r.clone()), rn_r)
}
pub fn gamma_limsup_ty() -> Expr {
let rn_r = fn_ty(vec_ty(), real_ty());
arrow(seq_ty(rn_r.clone()), rn_r)
}
pub fn gamma_converges_ty() -> Expr {
let rn_r = fn_ty(vec_ty(), real_ty());
arrow(seq_ty(rn_r.clone()), arrow(rn_r, prop()))
}
pub fn gamma_compactness_ty() -> Expr {
prop()
}
pub fn gamma_conv_minimisers_ty() -> Expr {
prop()
}
pub fn gamma_conv_stability_ty() -> Expr {
prop()
}
pub fn ekeland_variational_principle_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), prop())
}
pub fn borwein_preiss_principle_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), prop())
}
pub fn density_of_minimisers_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), prop())
}
pub fn star_shaped_minimisation_ty() -> Expr {
prop()
}
pub fn trust_region_principle_ty() -> Expr {
prop()
}
pub fn palais_smale_condition_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), prop())
}
pub fn cerami_condition_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), prop())
}
pub fn mountain_pass_theorem_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), prop())
}
pub fn saddle_point_theorem_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), prop())
}
pub fn linking_theorem_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), prop())
}
pub fn morse_index_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), fn_ty(vec_ty(), nat_ty()))
}
pub fn morse_inequalities_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), prop())
}
pub fn deformation_lemma_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), prop())
}
pub fn build_variational_analysis_env() -> Environment {
let mut env = Environment::new();
let axioms: &[(&str, Expr)] = &[
("RegularSubdifferential", regular_subdifferential_ty()),
("LimitingSubdifferential", limiting_subdifferential_ty()),
("ClarkeSubdifferential", clarke_subdifferential_ty()),
(
"ClarkeGeneralizedGradient",
clarke_generalized_gradient_ty(),
),
("MordukhovichCriterion", mordukhovich_criterion_ty()),
("LimitingNormalCone", limiting_normal_cone_ty()),
("SubdiffSumRule", subdiff_sum_rule_ty()),
("SubdiffChainRule", subdiff_chain_rule_ty()),
("FuzzySumRule", fuzzy_sum_rule_ty()),
("IsProxRegularSet", is_prox_regular_set_ty()),
("IsProxRegularFunction", is_prox_regular_function_ty()),
("ProxRegularityProjection", prox_regularity_projection_ty()),
("SubmootFunction", subsmooth_function_ty()),
("UniformlyProxRegular", uniformly_prox_regular_ty()),
("EpiConverges", epi_converges_ty()),
("EpiLimInf", epi_liminf_ty()),
("EpiLimSup", epi_limsup_ty()),
("MoscoConverges", mosco_converges_ty()),
("EpiConvImpliesMin", epi_conv_implies_min_ty()),
("MoscoImpliesEpi", mosco_implies_epi_ty()),
("GammaLimInf", gamma_liminf_ty()),
("GammaLimSup", gamma_limsup_ty()),
("GammaConverges", gamma_converges_ty()),
("GammaCompactness", gamma_compactness_ty()),
("GammaConvergenceMinimisers", gamma_conv_minimisers_ty()),
("GammaConvergenceStability", gamma_conv_stability_ty()),
(
"EkelandVariationalPrinciple",
ekeland_variational_principle_ty(),
),
("BorweinPreissPrinciple", borwein_preiss_principle_ty()),
("DensityOfMinimisers", density_of_minimisers_ty()),
("StarShapedMinimisation", star_shaped_minimisation_ty()),
("TrustRegionPrinciple", trust_region_principle_ty()),
("PalaisSmaleCondition", palais_smale_condition_ty()),
("CeramiCondition", cerami_condition_ty()),
("MountainPassTheorem", mountain_pass_theorem_ty()),
("SaddlePointTheorem", saddle_point_theorem_ty()),
("LinkingTheorem", linking_theorem_ty()),
("MorseIndex", morse_index_ty()),
("MorseInequalities", morse_inequalities_ty()),
("DeformationLemma", deformation_lemma_ty()),
];
for &(name, ref ty) in axioms {
env.add(Declaration::Axiom {
name: Name::str(name),
univ_params: vec![],
ty: ty.clone(),
})
.ok();
}
env
}
pub fn clarke_gradient_approx(f: impl Fn(&[f64]) -> f64, x: &[f64], epsilon: f64) -> Vec<f64> {
let n = x.len();
let mut grad = vec![0.0; n];
let h = epsilon;
for i in 0..n {
let mut xp = x.to_vec();
xp[i] += h;
let mut xm = x.to_vec();
xm[i] -= h;
grad[i] = (f(&xp) - f(&xm)) / (2.0 * h);
}
grad
}
pub fn check_regular_subgradient(
f: impl Fn(&[f64]) -> f64,
x: &[f64],
v: &[f64],
h: f64,
tol: f64,
) -> bool {
let n = x.len();
let fx = f(x);
for i in 0..n {
for sign in &[-1.0_f64, 1.0_f64] {
let mut d = vec![0.0; n];
d[i] = *sign;
let y: Vec<f64> = x.iter().zip(d.iter()).map(|(xi, di)| xi + h * di).collect();
let fy = f(&y);
let inner: f64 = v.iter().zip(d.iter()).map(|(vi, di)| vi * di * h).sum();
let ratio = (fy - fx - inner) / h;
if ratio < -tol {
return false;
}
}
}
true
}
pub fn ekeland_approximate_minimiser(
f: impl Fn(&[f64]) -> f64,
x0: &[f64],
eps: f64,
max_iter: usize,
) -> Vec<f64> {
let mut x = x0.to_vec();
let step = eps * 0.1;
let n = x.len();
let mut fx = f(&x);
for _ in 0..max_iter {
let mut improved = false;
for i in 0..n {
for &sign in &[-1.0_f64, 1.0_f64] {
let mut y = x.clone();
y[i] += sign * step;
let fy = f(&y);
let dist: f64 = x
.iter()
.zip(y.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
if fy + eps * dist < fx - 1e-12 {
fx = fy;
x = y;
improved = true;
}
}
}
if !improved {
break;
}
}
x
}
pub fn is_palais_smale_sequence(
f: impl Fn(&[f64]) -> f64,
grad_f: impl Fn(&[f64]) -> Vec<f64>,
sequence: &[Vec<f64>],
bound: f64,
) -> bool {
if sequence.is_empty() {
return true;
}
for x in sequence {
if f(x).abs() > bound {
return false;
}
}
let last = sequence
.last()
.expect("sequence is non-empty: checked by early return");
let g = grad_f(last);
let norm: f64 = g.iter().map(|gi| gi * gi).sum::<f64>().sqrt();
norm < 0.1 * bound.max(1.0)
}
pub fn penalty_indicator_1d(x: f64, a: f64, b: f64, n: f64) -> f64 {
n * (0.0_f64.max(x - b) + 0.0_f64.max(a - x))
}
pub fn check_gamma_convergence_indicator(x: f64, a: f64, b: f64, ns: &[f64]) -> bool {
let inside = x >= a && x <= b;
if inside {
ns.iter()
.all(|&n| (penalty_indicator_1d(x, a, b, n) - 0.0).abs() < 1e-12)
} else {
let vals: Vec<f64> = ns
.iter()
.map(|&n| penalty_indicator_1d(x, a, b, n))
.collect();
vals.windows(2).all(|w| w[1] >= w[0] - 1e-12)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_env_keys() {
let env = build_variational_analysis_env();
assert!(env.get(&Name::str("RegularSubdifferential")).is_some());
assert!(env.get(&Name::str("LimitingSubdifferential")).is_some());
assert!(env.get(&Name::str("ClarkeSubdifferential")).is_some());
assert!(env.get(&Name::str("EkelandVariationalPrinciple")).is_some());
assert!(env.get(&Name::str("MountainPassTheorem")).is_some());
assert!(env.get(&Name::str("PalaisSmaleCondition")).is_some());
assert!(env.get(&Name::str("GammaConverges")).is_some());
assert!(env.get(&Name::str("MoscoConverges")).is_some());
}
#[test]
fn test_regular_subgradient_smooth() {
let f = |x: &[f64]| x[0] * x[0];
let x = vec![1.0];
let v = vec![2.0];
assert!(check_regular_subgradient(f, &x, &v, 1e-4, 1e-5));
}
#[test]
fn test_clarke_gradient_smooth_quadratic() {
let f = |x: &[f64]| 0.5 * x[0] * x[0];
let x = vec![2.0];
let g = clarke_gradient_approx(f, &x, 1e-6);
assert!((g[0] - 2.0).abs() < 1e-4, "Clarke grad = {}", g[0]);
}
#[test]
fn test_epi_liminf_constant_sequence() {
let fns: Vec<Box<dyn Fn(&[f64]) -> f64 + Send + Sync>> = (0..5)
.map(|_| Box::new(|x: &[f64]| x[0] * x[0]) as Box<dyn Fn(&[f64]) -> f64 + Send + Sync>)
.collect();
let seq = FunctionSequence::new(fns);
let x = vec![0.0];
let val = seq.epi_liminf(&x, 0.5, 10);
assert!(val >= -1e-9, "epi-liminf at 0 = {val}");
assert!(val < 0.26, "epi-liminf at 0 too large: {val}");
}
#[test]
fn test_mountain_pass_geometry() {
let f = |x: &[f64]| x[0].powi(4) - 2.0 * x[0].powi(2);
let mut cfg = MountainPassConfig::new(vec![-1.0], vec![1.0], 100);
let pass_level = cfg.estimate_pass_level(&f);
assert!((pass_level - 0.0).abs() < 1e-9, "pass level = {pass_level}");
assert!(cfg.has_mountain_pass_geometry(&f));
}
#[test]
fn test_ekeland_approximate_minimiser() {
let f = |x: &[f64]| x[0] * x[0];
let x_eps = ekeland_approximate_minimiser(f, &[3.0], 1.0, 1000);
assert!(x_eps[0].abs() < 1.0, "Ekeland minimiser at {}", x_eps[0]);
}
#[test]
fn test_palais_smale_sequence() {
let f = |x: &[f64]| x[0] * x[0];
let grad_f = |x: &[f64]| vec![2.0 * x[0]];
let seq: Vec<Vec<f64>> = vec![vec![0.1], vec![0.01], vec![0.001]];
assert!(is_palais_smale_sequence(f, grad_f, &seq, 10.0));
}
#[test]
fn test_prox_regular_set() {
let pts: Vec<Vec<f64>> = (0..20)
.map(|k| {
let theta = k as f64 * std::f64::consts::TAU / 20.0;
vec![theta.cos(), theta.sin()]
})
.collect();
let prs = ProxRegularSet::new(pts, 0.5);
let x = vec![0.8, 0.0];
assert!(
prs.has_unique_projection(&x),
"near point should have unique projection"
);
}
#[test]
fn test_gamma_convergence_indicator() {
let ns = vec![1.0, 2.0, 5.0, 10.0, 50.0, 100.0];
assert!(check_gamma_convergence_indicator(0.5, 0.0, 1.0, &ns));
assert!(check_gamma_convergence_indicator(2.0, 0.0, 1.0, &ns));
}
}
pub fn build_env() -> oxilean_kernel::Environment {
build_variational_analysis_env()
}
pub fn topological_epi_convergence_ty() -> Expr {
let rn_r = fn_ty(vec_ty(), real_ty());
arrow(seq_ty(rn_r.clone()), arrow(rn_r, prop()))
}
pub fn mosco_convergence_reflexive_banach_ty() -> Expr {
prop()
}
pub fn epi_convergence_stability_ty() -> Expr {
prop()
}
pub fn metric_regularity_ty() -> Expr {
arrow(
fn_ty(vec_ty(), fn_ty(vec_ty(), prop())),
arrow(real_ty(), prop()),
)
}
pub fn linear_regularity_ty() -> Expr {
arrow(list_ty(fn_ty(vec_ty(), prop())), prop())
}
pub fn lipschitz_stability_ty() -> Expr {
arrow(fn_ty(vec_ty(), vec_ty()), arrow(real_ty(), prop()))
}
pub fn robinson_stability_ty() -> Expr {
arrow(fn_ty(vec_ty(), fn_ty(vec_ty(), prop())), prop())
}
pub fn aubin_property_criteria_ty() -> Expr {
arrow(fn_ty(vec_ty(), fn_ty(vec_ty(), prop())), prop())
}
pub fn clarke_subdiff_sum_rule_ty() -> Expr {
prop()
}
pub fn clarke_subdiff_chain_rule_ty() -> Expr {
prop()
}
pub fn mordukhovich_subdiff_sum_rule_ty() -> Expr {
prop()
}
pub fn mordukhovich_subdiff_chain_rule_ty() -> Expr {
prop()
}
pub fn clarke_stationary_condition_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), arrow(vec_ty(), prop()))
}
pub fn ekeland_with_error_bound_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), arrow(real_ty(), prop()))
}
pub fn borwein_preiss_smooth_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), prop())
}
pub fn flowering_principle_ty() -> Expr {
prop()
}
pub fn deville_godefroy_zizler_ty() -> Expr {
prop()
}
pub fn proximal_normal_cone_ty() -> Expr {
arrow(
fn_ty(vec_ty(), prop()),
arrow(vec_ty(), fn_ty(vec_ty(), prop())),
)
}
pub fn frechet_normal_cone_ty() -> Expr {
arrow(
fn_ty(vec_ty(), prop()),
arrow(vec_ty(), fn_ty(vec_ty(), prop())),
)
}
pub fn normal_cone_inclusion_ty() -> Expr {
prop()
}
pub fn normal_cone_calculus_ty() -> Expr {
arrow(
fn_ty(vec_ty(), prop()),
arrow(fn_ty(vec_ty(), prop()), prop()),
)
}
pub fn mordukhovich_coderivative_ty() -> Expr {
arrow(
fn_ty(vec_ty(), vec_ty()),
arrow(vec_ty(), fn_ty(vec_ty(), fn_ty(vec_ty(), prop()))),
)
}
pub fn strict_differentiability_ty() -> Expr {
arrow(fn_ty(vec_ty(), vec_ty()), arrow(vec_ty(), prop()))
}
pub fn clarke_generalised_jacobian_ty() -> Expr {
arrow(fn_ty(vec_ty(), vec_ty()), arrow(vec_ty(), type0()))
}
pub fn hausdorff_continuity_ty() -> Expr {
arrow(fn_ty(vec_ty(), fn_ty(vec_ty(), prop())), prop())
}
pub fn berge_maximum_theorem_ty() -> Expr {
arrow(
fn_ty(vec_ty(), real_ty()),
arrow(fn_ty(vec_ty(), fn_ty(vec_ty(), prop())), prop()),
)
}
pub fn michael_selection_theorem_ty() -> Expr {
arrow(fn_ty(vec_ty(), fn_ty(vec_ty(), prop())), prop())
}
pub fn kuratowski_ryll_nardzewski_ty() -> Expr {
arrow(fn_ty(vec_ty(), fn_ty(vec_ty(), prop())), prop())
}
pub fn proximal_point_algorithm_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), prop())
}
pub fn bundle_method_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), prop())
}
pub fn prox_regular_minimisation_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), arrow(real_ty(), prop()))
}
pub fn douglas_rachford_splitting_ty() -> Expr {
let rn_r = fn_ty(vec_ty(), real_ty());
arrow(rn_r.clone(), arrow(rn_r, prop()))
}
pub fn upper_dini_derivative_ty() -> Expr {
arrow(
fn_ty(vec_ty(), real_ty()),
arrow(vec_ty(), arrow(vec_ty(), real_ty())),
)
}
pub fn lower_dini_derivative_ty() -> Expr {
arrow(
fn_ty(vec_ty(), real_ty()),
arrow(vec_ty(), arrow(vec_ty(), real_ty())),
)
}
pub fn quasidifferential_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), arrow(vec_ty(), type0()))
}
pub fn clarke_directional_derivative_ty() -> Expr {
arrow(
fn_ty(vec_ty(), real_ty()),
arrow(vec_ty(), arrow(vec_ty(), real_ty())),
)
}
pub fn gradient_flow_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), prop())
}
pub fn bregman_dynamics_ty() -> Expr {
let rn_r = fn_ty(vec_ty(), real_ty());
arrow(rn_r.clone(), arrow(rn_r, prop()))
}
pub fn saddle_point_dynamics_ty() -> Expr {
arrow(fn_ty(vec_ty(), fn_ty(vec_ty(), real_ty())), prop())
}
pub fn mirror_descent_algorithm_ty() -> Expr {
let rn_r = fn_ty(vec_ty(), real_ty());
arrow(rn_r.clone(), arrow(rn_r, prop()))
}
pub fn is_quasiconvex_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), prop())
}
pub fn is_pseudoconvex_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), prop())
}
pub fn quasiconvex_level_sets_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), arrow(real_ty(), prop()))
}
pub fn strict_quasiconvexity_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), prop())
}
pub fn mfcq_ty() -> Expr {
let rn_r = fn_ty(vec_ty(), real_ty());
let constraint_list = list_ty(rn_r.clone());
arrow(
constraint_list.clone(),
arrow(constraint_list, arrow(vec_ty(), prop())),
)
}
pub fn licq_ty() -> Expr {
let rn_r = fn_ty(vec_ty(), real_ty());
arrow(list_ty(rn_r), arrow(vec_ty(), prop()))
}
pub fn second_order_sufficient_conditions_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), arrow(vec_ty(), prop()))
}
pub fn second_order_necessary_conditions_ty() -> Expr {
arrow(fn_ty(vec_ty(), real_ty()), arrow(vec_ty(), prop()))
}
pub fn kkt_conditions_ty() -> Expr {
let rn_r = fn_ty(vec_ty(), real_ty());
arrow(rn_r.clone(), arrow(list_ty(rn_r), arrow(vec_ty(), prop())))
}
pub fn build_variational_analysis_env_extended() -> Environment {
let mut env = build_variational_analysis_env();
let axioms: &[(&str, Expr)] = &[
(
"TopologicalEpiConvergence",
topological_epi_convergence_ty(),
),
(
"MoscoConvergenceReflexiveBanach",
mosco_convergence_reflexive_banach_ty(),
),
("EpiConvergenceStability", epi_convergence_stability_ty()),
("MetricRegularity", metric_regularity_ty()),
("LinearRegularity", linear_regularity_ty()),
("LipschitzStability", lipschitz_stability_ty()),
("RobinsonStability", robinson_stability_ty()),
("AubinPropertyCriteria", aubin_property_criteria_ty()),
("ClarkeSubdiffSumRule", clarke_subdiff_sum_rule_ty()),
("ClarkeSubdiffChainRule", clarke_subdiff_chain_rule_ty()),
(
"MordukhovichSubdiffSumRule",
mordukhovich_subdiff_sum_rule_ty(),
),
(
"MordukhovichSubdiffChainRule",
mordukhovich_subdiff_chain_rule_ty(),
),
(
"ClarkeStationaryCondition",
clarke_stationary_condition_ty(),
),
("EkelandWithErrorBound", ekeland_with_error_bound_ty()),
("BorweinPreissSmooth", borwein_preiss_smooth_ty()),
("FloweringPrinciple", flowering_principle_ty()),
("DevilleGodefroyZizler", deville_godefroy_zizler_ty()),
("ProximalNormalCone", proximal_normal_cone_ty()),
("FrechetNormalCone", frechet_normal_cone_ty()),
("NormalConeInclusion", normal_cone_inclusion_ty()),
("NormalConeCalculus", normal_cone_calculus_ty()),
("MordukhovichCoderivative", mordukhovich_coderivative_ty()),
("StrictDifferentiability", strict_differentiability_ty()),
(
"ClarkeGeneralisedJacobian",
clarke_generalised_jacobian_ty(),
),
("HausdorffContinuity", hausdorff_continuity_ty()),
("BergeMaximumTheorem", berge_maximum_theorem_ty()),
("MichaelSelectionTheorem", michael_selection_theorem_ty()),
("KuratowskiRyllNardzewski", kuratowski_ryll_nardzewski_ty()),
("ProximalPointAlgorithm", proximal_point_algorithm_ty()),
("BundleMethod", bundle_method_ty()),
("ProxRegularMinimisation", prox_regular_minimisation_ty()),
("DouglasRachfordSplitting", douglas_rachford_splitting_ty()),
("UpperDiniDerivative", upper_dini_derivative_ty()),
("LowerDiniDerivative", lower_dini_derivative_ty()),
("Quasidifferential", quasidifferential_ty()),
(
"ClarkeDirectionalDerivative",
clarke_directional_derivative_ty(),
),
("GradientFlow", gradient_flow_ty()),
("BregmanDynamics", bregman_dynamics_ty()),
("SaddlePointDynamics", saddle_point_dynamics_ty()),
("MirrorDescentAlgorithm", mirror_descent_algorithm_ty()),
("IsQuasiconvex", is_quasiconvex_ty()),
("IsPseudoconvex", is_pseudoconvex_ty()),
("QuasiconvexLevelSets", quasiconvex_level_sets_ty()),
("StrictQuasiconvexity", strict_quasiconvexity_ty()),
("MFCQ", mfcq_ty()),
("LICQ", licq_ty()),
(
"SecondOrderSufficientConditions",
second_order_sufficient_conditions_ty(),
),
(
"SecondOrderNecessaryConditions",
second_order_necessary_conditions_ty(),
),
("KKTConditions", kkt_conditions_ty()),
];
for &(name, ref ty) in axioms {
env.add(Declaration::Axiom {
name: Name::str(name),
univ_params: vec![],
ty: ty.clone(),
})
.ok();
}
env
}
#[cfg(test)]
mod tests_extended {
use super::*;
#[test]
fn test_extended_env_axioms_registered() {
let env = build_variational_analysis_env_extended();
assert!(env.get(&Name::str("MetricRegularity")).is_some());
assert!(env.get(&Name::str("LinearRegularity")).is_some());
assert!(env.get(&Name::str("MichaelSelectionTheorem")).is_some());
assert!(env.get(&Name::str("BergeMaximumTheorem")).is_some());
assert!(env.get(&Name::str("ProximalPointAlgorithm")).is_some());
assert!(env.get(&Name::str("DouglasRachfordSplitting")).is_some());
assert!(env.get(&Name::str("IsQuasiconvex")).is_some());
assert!(env.get(&Name::str("KKTConditions")).is_some());
assert!(env.get(&Name::str("MFCQ")).is_some());
assert!(env.get(&Name::str("LICQ")).is_some());
assert!(env.get(&Name::str("GradientFlow")).is_some());
assert!(env.get(&Name::str("BregmanDynamics")).is_some());
}
#[test]
fn test_ekeland_principle_smooth_quadratic() {
let f = |x: &[f64]| x[0] * x[0];
let ep = EkelandPrinciple::new(0.5, 500);
let x_eps = ep.find_minimiser(f, &[2.0]);
assert!(x_eps[0].abs() < 2.0, "Ekeland minimiser: {}", x_eps[0]);
let samples: Vec<Vec<f64>> = (0..5).map(|i| vec![i as f64 * 0.5]).collect();
assert!(ep.verify_ekeland_condition(f, &x_eps, &samples));
}
#[test]
fn test_mordukhovich_subdiff_stationary() {
let f = |x: &[f64]| x[0] * x[0];
let approx = MordukhovichSubdiffApprox::new(1e-5, 10, 1e-3);
assert!(approx.is_stationary(f, &[0.0]));
assert!(!approx.is_stationary(f, &[1.0]));
}
#[test]
fn test_mordukhovich_subdiff_frechet_quadratic() {
let f = |x: &[f64]| 0.5 * x[0] * x[0];
let approx = MordukhovichSubdiffApprox::new(1e-5, 5, 0.01);
let g = approx.frechet_subgradient(f, &[3.0]);
assert!(
(g[0] - 3.0).abs() < 1e-3,
"gradient at 3 should be ≈3, got {}",
g[0]
);
}
#[test]
fn test_proximal_point_solver_convergence() {
let f = |x: &[f64]| x[0] * x[0];
let solver = ProximalPointSolver::new(0.5, 50, 1e-4);
let iterates = solver.solve(f, &[3.0]);
assert!(!iterates.is_empty());
let last = iterates.last().expect("last should succeed");
assert!(
last[0].abs() < 3.0,
"PPA should move towards 0, got {}",
last[0]
);
}
#[test]
fn test_proximal_point_solver_converged() {
let f = |x: &[f64]| x[0] * x[0];
let solver = ProximalPointSolver::new(1.0, 200, 1e-3);
let iterates = solver.solve(f, &[1.0]);
assert!(
solver.has_converged(&iterates),
"PPA should converge for convex quadratic"
);
}
#[test]
fn test_metric_regularity_checker_quasiconvex() {
let f = |x: &[f64]| x[0] * x[0];
let result = MetricRegularityChecker::check_quasiconvex(f, &[-1.0], &[1.0], 20);
assert!(result, "x^2 should be quasiconvex");
}
#[test]
fn test_metric_regularity_checker_not_quasiconvex() {
let f = |x: &[f64]| -(x[0] * x[0]);
let result = MetricRegularityChecker::check_quasiconvex(f, &[-1.0], &[1.0], 20);
assert!(!result, "-(x^2) should NOT be quasiconvex");
}
#[test]
fn test_metric_regularity_checker_cq() {
let constraints: Vec<Box<dyn Fn(&[f64]) -> f64>> = vec![Box::new(|x: &[f64]| x[0])];
let x = vec![0.0];
let result =
MetricRegularityChecker::check_constraint_qualification(&constraints, &x, 1e-5);
assert!(result, "Single nonzero-gradient constraint satisfies CQ");
}
#[test]
fn test_ekeland_near_minimality_gap() {
let f = |x: &[f64]| (x[0] - 2.0).powi(2);
let ep = EkelandPrinciple::new(0.1, 1000);
let x_eps = ep.find_minimiser(f, &[5.0]);
let gap = ep.near_minimality_gap(f, &x_eps, &[5.0]);
assert!(gap >= 0.0, "gap should be non-negative: {gap}");
}
#[test]
fn test_build_variational_env_original_still_present() {
let env = build_variational_analysis_env_extended();
assert!(env.get(&Name::str("RegularSubdifferential")).is_some());
assert!(env.get(&Name::str("EkelandVariationalPrinciple")).is_some());
assert!(env.get(&Name::str("MountainPassTheorem")).is_some());
assert!(env.get(&Name::str("GammaConverges")).is_some());
}
}
#[cfg(test)]
mod tests_variational_extended {
use super::*;
#[test]
fn test_prox_l1_soft_threshold() {
let prox = ProximalOperator::new(0.5, ProxFnType::L1Norm);
assert!((prox.apply_scalar(1.5) - 1.0).abs() < 1e-10);
assert!((prox.apply_scalar(0.3) - 0.0).abs() < 1e-10);
}
#[test]
fn test_prox_l2_shrinkage() {
let prox = ProximalOperator::new(1.0, ProxFnType::L2NormSquared);
assert!((prox.apply_scalar(3.0) - 1.0).abs() < 1e-10);
}
#[test]
fn test_prox_nonneg() {
let prox = ProximalOperator::new(1.0, ProxFnType::NonNegativeOrtHant);
assert!((prox.apply_scalar(-3.0) - 0.0).abs() < 1e-10);
assert!((prox.apply_scalar(2.0) - 2.0).abs() < 1e-10);
}
#[test]
fn test_moreau_decomposition() {
let prox = ProximalOperator::new(1.0, ProxFnType::L1Norm);
let v = 2.5;
let (p, d) = prox.moreau_decomposition(v);
assert!((p + d - v).abs() < 1e-10);
}
#[test]
fn test_admm_dual_update() {
let admm = ADMMSolver::new(1.0, 100, 1e-6);
let y_new = admm.dual_update(0.5, 0.1);
assert!((y_new - 0.6).abs() < 1e-10);
}
#[test]
fn test_admm_stopping() {
let admm = ADMMSolver::new(1.0, 100, 1e-6);
assert!(admm.stopping_criteria(1e-7, 1e-7));
assert!(!admm.stopping_criteria(1e-5, 1e-7));
}
#[test]
fn test_gradient_descent_rate() {
let f = ConvexSubdifferential::new("quadratic")
.with_differentiability()
.with_strong_convexity(1.0)
.with_lipschitz(10.0);
let rate = f
.gradient_descent_rate()
.expect("gradient_descent_rate should succeed");
assert!((rate - 0.9).abs() < 1e-10);
}
}