use super::functions::*;
use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
#[allow(dead_code)]
pub struct ADMMSolver {
pub rho: f64,
pub max_iter: usize,
pub tol: f64,
}
#[allow(dead_code)]
impl ADMMSolver {
pub fn new(rho: f64, max_iter: usize, tol: f64) -> Self {
ADMMSolver { rho, max_iter, tol }
}
pub fn lasso_convergence_rate(&self, lambda: f64, _n: usize) -> f64 {
1.0 - 1.0 / (1.0 + lambda / self.rho)
}
pub fn dual_update(&self, y: f64, primal_residual: f64) -> f64 {
y + self.rho * primal_residual
}
pub fn stopping_criteria(&self, primal_res: f64, dual_res: f64) -> bool {
primal_res < self.tol && dual_res < self.tol
}
pub fn convergence_bound_at(&self, k: usize, _initial_gap: f64) -> f64 {
1.0 / k.max(1) as f64
}
}
pub struct InfConvolution {
pub f: String,
pub g: String,
}
impl InfConvolution {
pub fn new(f: impl Into<String>, g: impl Into<String>) -> Self {
Self {
f: f.into(),
g: g.into(),
}
}
pub fn is_convex_if_both_convex(&self) -> bool {
true
}
pub fn epigraph_sum(&self) -> String {
format!(
"Epigraph sum: epi({} □ {}) = epi({}) + epi({}) (Minkowski sum of epigraphs). \
This is why the infimal convolution is also called the epigraph sum.",
self.f, self.g, self.f, self.g
)
}
}
pub struct FenchelDuality {
pub f: String,
pub g: String,
}
impl FenchelDuality {
pub fn new(f: impl Into<String>, g: impl Into<String>) -> Self {
Self {
f: f.into(),
g: g.into(),
}
}
pub fn strong_duality_holds(&self) -> bool {
true
}
pub fn optimal_condition(&self) -> String {
format!(
"Fenchel-Rockafellar strong duality: If dom({}) ∩ int(dom({})) ≠ ∅, then \
inf_x({}(x) + {}(Ax)) = max_y(-{}*(-A*y) - {}*(y)), \
and the gap is zero.",
self.f, self.g, self.f, self.g, self.f, self.g
)
}
}
#[allow(dead_code)]
pub struct ConvexSubdifferential {
pub name: String,
pub is_differentiable: bool,
pub strong_convexity_modulus: f64,
pub is_lipschitz: bool,
pub lipschitz_constant: f64,
}
#[allow(dead_code)]
impl ConvexSubdifferential {
pub fn new(name: &str) -> Self {
ConvexSubdifferential {
name: name.to_string(),
is_differentiable: false,
strong_convexity_modulus: 0.0,
is_lipschitz: false,
lipschitz_constant: 0.0,
}
}
pub fn with_differentiability(mut self) -> Self {
self.is_differentiable = true;
self
}
pub fn with_strong_convexity(mut self, mu: f64) -> Self {
self.strong_convexity_modulus = mu;
self
}
pub fn with_lipschitz(mut self, l: f64) -> Self {
self.is_lipschitz = true;
self.lipschitz_constant = l;
self
}
pub fn sum_rule_holds(&self, other: &ConvexSubdifferential) -> bool {
self.is_differentiable || other.is_differentiable
}
pub fn chain_rule_holds(&self) -> bool {
self.is_lipschitz
}
pub fn gradient_descent_rate(&self) -> Option<f64> {
if self.strong_convexity_modulus > 0.0 && self.lipschitz_constant > 0.0 {
Some(1.0 - self.strong_convexity_modulus / self.lipschitz_constant)
} else {
None
}
}
pub fn proximal_convergence_rate(&self, k: usize) -> f64 {
if self.strong_convexity_modulus > 0.0 {
let lambda = 1.0 / (2.0 * self.lipschitz_constant.max(1.0));
(1.0 - lambda * self.strong_convexity_modulus)
.powi(k as i32)
.abs()
} else {
1.0 / k.max(1) as f64
}
}
}
pub struct Epigraph {
pub function: String,
}
impl Epigraph {
pub fn new(function: impl Into<String>) -> Self {
Self {
function: function.into(),
}
}
pub fn is_closed_iff_lsc(&self) -> String {
format!(
"Theorem: epi({}) = {{(x,α) | {}(x) ≤ α}} is a closed set in X×ℝ \
if and only if {} is lower semicontinuous.",
self.function, self.function, self.function
)
}
pub fn convex_iff_fn_convex(&self) -> String {
format!(
"Theorem: epi({}) is a convex set iff {} is a convex function \
(Jensen's inequality characterisation).",
self.function, self.function
)
}
}
pub struct ProximalPointSolver {
pub lambda: f64,
pub max_iter: usize,
pub tol: f64,
}
impl ProximalPointSolver {
pub fn new(lambda: f64, max_iter: usize, tol: f64) -> Self {
Self {
lambda,
max_iter,
tol,
}
}
pub fn prox_step(&self, f: impl Fn(&[f64]) -> f64 + Copy, v: &[f64]) -> Vec<f64> {
let n = v.len();
let mut x = v.to_vec();
let inner_step = self.lambda * 0.01;
let inner_iters = 500;
for _ in 0..inner_iters {
let grad_f = clarke_gradient_approx(f, &x, 1e-6);
let mut moved = false;
for i in 0..n {
let total_grad = grad_f[i] + (x[i] - v[i]) / self.lambda;
let new_xi = x[i] - inner_step * total_grad;
if (new_xi - x[i]).abs() > 1e-14 {
moved = true;
}
x[i] = new_xi;
}
if !moved {
break;
}
}
x
}
pub fn solve(&self, f: impl Fn(&[f64]) -> f64 + Copy, x0: &[f64]) -> Vec<Vec<f64>> {
let mut iterates = vec![x0.to_vec()];
let mut x = x0.to_vec();
for _ in 0..self.max_iter {
let x_new = self.prox_step(f, &x);
let diff: f64 = x
.iter()
.zip(x_new.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
iterates.push(x_new.clone());
x = x_new;
if diff < self.tol {
break;
}
}
iterates
}
pub fn has_converged(&self, iterates: &[Vec<f64>]) -> bool {
if iterates.len() < 2 {
return false;
}
let last = iterates
.last()
.expect("iterates has at least 2 elements: checked by early return");
let prev = &iterates[iterates.len() - 2];
let diff: f64 = last
.iter()
.zip(prev.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
diff < self.tol
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub enum ProxFnType {
L1Norm,
L2NormSquared,
NonNegativeOrtHant,
L2Ball { radius: f64 },
}
pub struct ProximalMapping {
pub f: String,
pub lambda: f64,
}
impl ProximalMapping {
pub fn new(f: impl Into<String>, lambda: f64) -> Self {
Self {
f: f.into(),
lambda,
}
}
pub fn prox_point(&self) -> String {
format!(
"prox_{{λ{}}}(x) = argmin_y {{ {}(y) + ||y - x||²/(2·{:.4}) }}. \
Moreau (1962): this is always uniquely defined for proper lsc convex f.",
self.f, self.f, self.lambda
)
}
pub fn firm_nonexpansive(&self) -> String {
format!(
"The proximal mapping prox_{{λ·{}}} is firmly nonexpansive: \
||prox(x) - prox(y)||² ≤ ⟨prox(x)-prox(y), x-y⟩ for all x,y.",
self.f
)
}
}
pub struct FunctionSequence {
functions: Vec<Box<dyn Fn(&[f64]) -> f64 + Send + Sync>>,
}
impl FunctionSequence {
pub fn new(functions: Vec<Box<dyn Fn(&[f64]) -> f64 + Send + Sync>>) -> Self {
Self { functions }
}
pub fn len(&self) -> usize {
self.functions.len()
}
pub fn is_empty(&self) -> bool {
self.functions.is_empty()
}
pub fn eval(&self, n: usize, x: &[f64]) -> f64 {
self.functions[n](x)
}
pub fn epi_liminf(&self, x: &[f64], radius: f64, grid_steps: usize) -> f64 {
let n_fns = self.functions.len();
if n_fns == 0 {
return f64::INFINITY;
}
let mut result = f64::INFINITY;
let step = if grid_steps > 0 {
2.0 * radius / grid_steps as f64
} else {
radius
};
let dim = x.len();
let perturb_count = if dim == 1 { grid_steps + 1 } else { grid_steps };
for k in 0..perturb_count {
let mut y = x.to_vec();
if dim >= 1 {
y[0] = x[0] - radius + k as f64 * step;
}
let mut liminf_n = f64::INFINITY;
for fn_n in &self.functions {
let val = fn_n(&y);
if val < liminf_n {
liminf_n = val;
}
}
if liminf_n < result {
result = liminf_n;
}
}
result
}
pub fn epi_limsup(&self, x: &[f64], radius: f64, grid_steps: usize) -> f64 {
let n_fns = self.functions.len();
if n_fns == 0 {
return f64::NEG_INFINITY;
}
let step = if grid_steps > 0 {
2.0 * radius / grid_steps as f64
} else {
radius
};
let dim = x.len();
let mut result = f64::NEG_INFINITY;
let perturb_count = if dim == 1 { grid_steps + 1 } else { grid_steps };
for k in 0..perturb_count {
let mut y = x.to_vec();
if dim >= 1 {
y[0] = x[0] - radius + k as f64 * step;
}
let mut limsup_n = f64::NEG_INFINITY;
for fn_n in &self.functions {
let val = fn_n(&y);
if val > limsup_n {
limsup_n = val;
}
}
if limsup_n > result {
result = limsup_n;
}
}
result
}
pub fn gamma_liminf(&self, x: &[f64], radii: &[f64]) -> f64 {
let mut best = f64::NEG_INFINITY;
for &r in radii {
let steps = 20;
let step = 2.0 * r / steps as f64;
let mut inf_over_n = f64::INFINITY;
for n_idx in 0..self.functions.len() {
let mut local_inf = f64::INFINITY;
for k in 0..=steps {
let mut y = x.to_vec();
if !y.is_empty() {
y[0] = x[0] - r + k as f64 * step;
}
let val = self.functions[n_idx](&y);
if val < local_inf {
local_inf = val;
}
}
if n_idx == 0 || local_inf < inf_over_n {
if local_inf < inf_over_n {
inf_over_n = local_inf;
}
}
}
if inf_over_n > best {
best = inf_over_n;
}
}
best
}
}
#[derive(Debug, Clone)]
pub struct ProxRegularSet {
pub boundary_points: Vec<Vec<f64>>,
pub radius: f64,
}
impl ProxRegularSet {
pub fn new(boundary_points: Vec<Vec<f64>>, radius: f64) -> Self {
Self {
boundary_points,
radius,
}
}
pub fn project(&self, x: &[f64]) -> Vec<f64> {
let mut best_dist = f64::INFINITY;
let mut best = x.to_vec();
for p in &self.boundary_points {
let dist: f64 = x
.iter()
.zip(p.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
if dist < best_dist {
best_dist = dist;
best = p.clone();
}
}
best
}
pub fn has_unique_projection(&self, x: &[f64]) -> bool {
let mut dists: Vec<f64> = self
.boundary_points
.iter()
.map(|p| {
x.iter()
.zip(p.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt()
})
.collect();
dists.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
if dists.len() < 2 {
return true;
}
(dists[0] - dists[1]).abs() > 1e-9
}
pub fn check_prox_regular(&self, test_points: &[Vec<f64>]) -> bool {
for x in test_points {
let dist_to_set = self
.boundary_points
.iter()
.map(|p| {
x.iter()
.zip(p.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt()
})
.fold(f64::INFINITY, f64::min);
if dist_to_set < self.radius && !self.has_unique_projection(x) {
return false;
}
}
true
}
}
pub struct VariationalInequality {
pub operator: String,
pub domain: String,
}
impl VariationalInequality {
pub fn new(operator: impl Into<String>, domain: impl Into<String>) -> Self {
Self {
operator: operator.into(),
domain: domain.into(),
}
}
pub fn minty_stampacchia(&self) -> String {
format!(
"Minty-Stampacchia (1960/1964): For the VI with F='{}' on C='{}': \
if F is monotone and hemicontinuous, then x* satisfies the VI iff \
⟨F(y), y-x*⟩ ≥ 0 for all y ∈ C (Minty formulation).",
self.operator, self.domain
)
}
pub fn existence_condition(&self) -> String {
format!(
"Existence (Hartman-Stampacchia 1966): If C='{}' is compact convex and \
F='{}' is continuous, then the VI has at least one solution. \
For unbounded C, a coercivity condition ⟨F(x),x-x₀⟩/||x||→∞ suffices.",
self.domain, self.operator
)
}
}
pub struct Subdifferential {
pub point: Vec<f64>,
}
impl Subdifferential {
pub fn new(point: Vec<f64>) -> Self {
Self { point }
}
pub fn is_nonempty_at_interior(&self) -> bool {
true
}
pub fn optimality_condition(&self) -> String {
let x_str: Vec<String> = self.point.iter().map(|v| format!("{:.3}", v)).collect();
format!(
"Fermat's rule: x = ({}) is a minimiser of f iff 0 ∈ ∂f(x). \
For a smooth convex f this reduces to ∇f(x) = 0.",
x_str.join(", ")
)
}
}
pub struct MordukhovichSubdiffApprox {
pub h: f64,
pub num_dirs: usize,
pub tolerance: f64,
}
impl MordukhovichSubdiffApprox {
pub fn new(h: f64, num_dirs: usize, tolerance: f64) -> Self {
Self {
h,
num_dirs,
tolerance,
}
}
pub fn frechet_subgradient(&self, f: impl Fn(&[f64]) -> f64, x: &[f64]) -> Vec<f64> {
clarke_gradient_approx(f, x, self.h)
}
pub fn limiting_subdiff_approx(
&self,
f: impl Fn(&[f64]) -> f64 + Copy,
x: &[f64],
) -> Vec<Vec<f64>> {
let n = x.len();
let mut result = Vec::new();
result.push(self.frechet_subgradient(f, x));
let mut lcg = crate::random_matrix_theory::Lcg::new(42);
for _ in 0..self.num_dirs {
let mut y = x.to_vec();
for yi in y.iter_mut() {
*yi += (lcg.next_f64() - 0.5) * 2.0 * self.tolerance;
}
let g = self.frechet_subgradient(f, &y);
let dist: f64 = (0..n).map(|i| (y[i] - x[i]).powi(2)).sum::<f64>().sqrt();
if dist < self.tolerance {
result.push(g);
}
}
result
}
pub fn is_stationary(&self, f: impl Fn(&[f64]) -> f64 + Copy, x: &[f64]) -> bool {
let g = self.frechet_subgradient(f, x);
let norm: f64 = g.iter().map(|gi| gi * gi).sum::<f64>().sqrt();
norm < self.tolerance
}
}
pub struct SetValuedMap {
pub domain: Vec<f64>,
pub values: Vec<Vec<f64>>,
}
impl SetValuedMap {
pub fn new(domain: Vec<f64>, values: Vec<Vec<f64>>) -> Self {
Self { domain, values }
}
pub fn upper_semicontinuous(&self) -> bool {
!self.values.iter().any(|v| v.is_empty())
}
pub fn closed_values(&self) -> bool {
!self.values.is_empty() && self.values.iter().all(|v| !v.is_empty())
}
}
pub struct MoreauEnvelope {
pub f: String,
pub lambda: f64,
}
impl MoreauEnvelope {
pub fn new(f: impl Into<String>, lambda: f64) -> Self {
Self {
f: f.into(),
lambda,
}
}
pub fn is_smooth(&self) -> bool {
true
}
pub fn gradient_formula(&self) -> String {
format!(
"∇(e_{{λ{}}})(x) = (x - prox_{{λ{}}}(x)) / {:.4}. \
This gradient is (1/λ)-Lipschitz, making e_{{λf}} a smooth approximation of {}.",
self.f, self.f, self.lambda, self.f
)
}
}
#[derive(Debug, Clone)]
pub struct MountainPassConfig {
pub a: Vec<f64>,
pub b: Vec<f64>,
pub estimated_pass_level: f64,
pub path_samples: usize,
}
impl MountainPassConfig {
pub fn new(a: Vec<f64>, b: Vec<f64>, path_samples: usize) -> Self {
Self {
a: a.clone(),
b: b.clone(),
estimated_pass_level: 0.0,
path_samples,
}
}
pub fn estimate_pass_level(&mut self, f: &impl Fn(&[f64]) -> f64) -> f64 {
let n = self.a.len();
let mut max_val = f64::NEG_INFINITY;
for k in 0..=self.path_samples {
let t = k as f64 / self.path_samples as f64;
let x: Vec<f64> = (0..n)
.map(|i| (1.0 - t) * self.a[i] + t * self.b[i])
.collect();
let val = f(&x);
if val > max_val {
max_val = val;
}
}
self.estimated_pass_level = max_val;
max_val
}
pub fn has_mountain_pass_geometry(&self, f: &impl Fn(&[f64]) -> f64) -> bool {
let fa = f(&self.a);
let fb = f(&self.b);
fa < self.estimated_pass_level && fb < self.estimated_pass_level
}
}
pub struct ConvexFunction {
pub domain: String,
pub is_proper: bool,
pub is_lsc: bool,
}
impl ConvexFunction {
pub fn new(domain: impl Into<String>, is_proper: bool, is_lsc: bool) -> Self {
Self {
domain: domain.into(),
is_proper,
is_lsc,
}
}
pub fn subdifferential(&self) -> String {
if self.is_proper && self.is_lsc {
format!(
"For a proper lsc convex function on '{}': the subdifferential ∂f(x) is \
nonempty on the interior of dom(f), and f = f** (Fenchel-Moreau theorem).",
self.domain
)
} else {
format!(
"For a convex function on '{}': subdifferential ∂f(x) = {{ v | ∀y, f(y) ≥ f(x) + ⟨v,y-x⟩ }}.",
self.domain
)
}
}
pub fn conjugate_fn(&self) -> String {
format!(
"Fenchel conjugate f*(v) = sup_{{x ∈ {}}} (⟨v,x⟩ - f(x)). \
For proper lsc convex f: f** = f (Fenchel-Moreau, 1949).",
self.domain
)
}
}
pub struct EkelandPrinciple {
pub epsilon: f64,
pub max_iter: usize,
pub step: f64,
}
impl EkelandPrinciple {
pub fn new(epsilon: f64, max_iter: usize) -> Self {
Self {
epsilon,
max_iter,
step: epsilon * 0.1,
}
}
pub fn find_minimiser(&self, f: impl Fn(&[f64]) -> f64, x0: &[f64]) -> Vec<f64> {
ekeland_approximate_minimiser(f, x0, self.epsilon, self.max_iter)
}
pub fn verify_ekeland_condition(
&self,
f: impl Fn(&[f64]) -> f64,
x_eps: &[f64],
sample_points: &[Vec<f64>],
) -> bool {
let f_eps = f(x_eps);
for x in sample_points {
let fx = f(x);
let dist: f64 = x_eps
.iter()
.zip(x.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
if fx + self.epsilon * dist < f_eps - 1e-12 {
return false;
}
}
true
}
pub fn near_minimality_gap(&self, f: impl Fn(&[f64]) -> f64, x_eps: &[f64], x0: &[f64]) -> f64 {
let f_eps = f(x_eps);
let f_x0 = f(x0);
f_x0 - f_eps
}
}
#[allow(dead_code)]
pub struct ProximalOperator {
pub lambda: f64,
pub fn_type: ProxFnType,
}
#[allow(dead_code)]
impl ProximalOperator {
pub fn new(lambda: f64, fn_type: ProxFnType) -> Self {
ProximalOperator { lambda, fn_type }
}
pub fn apply_scalar(&self, v: f64) -> f64 {
match &self.fn_type {
ProxFnType::L1Norm => {
let threshold = self.lambda;
v.signum() * (v.abs() - threshold).max(0.0)
}
ProxFnType::L2NormSquared => v / (1.0 + 2.0 * self.lambda),
ProxFnType::NonNegativeOrtHant => v.max(0.0),
ProxFnType::L2Ball { radius } => v.clamp(-radius, *radius),
}
}
pub fn apply_vector(&self, v: &[f64]) -> Vec<f64> {
match &self.fn_type {
ProxFnType::L2Ball { radius } => {
let norm: f64 = v.iter().map(|&x| x * x).sum::<f64>().sqrt();
if norm <= *radius {
v.to_vec()
} else {
v.iter().map(|&x| x * radius / norm).collect()
}
}
_ => v.iter().map(|&vi| self.apply_scalar(vi)).collect(),
}
}
pub fn moreau_decomposition(&self, v: f64) -> (f64, f64) {
let p = self.apply_scalar(v);
let dual = v - p;
(p, dual)
}
}
pub struct MetricRegularityChecker {
pub radius: f64,
pub num_tests: usize,
pub tol: f64,
}
impl MetricRegularityChecker {
pub fn new(radius: f64, num_tests: usize, tol: f64) -> Self {
Self {
radius,
num_tests,
tol,
}
}
pub fn estimate_modulus(
&self,
f: impl Fn(&[f64]) -> Vec<f64>,
x0: &[f64],
y0: &[f64],
domain_samples: &[Vec<f64>],
) -> f64 {
let mut max_ratio = 0.0_f64;
let mut lcg = crate::random_matrix_theory::Lcg::new(7);
for _ in 0..self.num_tests {
let x_pert: Vec<f64> = x0
.iter()
.map(|&xi| xi + (lcg.next_f64() - 0.5) * 2.0 * self.radius)
.collect();
let fx_pert = f(&x_pert);
let d_y_fx: f64 = y0
.iter()
.zip(fx_pert.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
if d_y_fx < 1e-14 {
continue;
}
let d_x_finv = domain_samples
.iter()
.filter(|s| {
let fs = f(s);
let d: f64 = y0
.iter()
.zip(fs.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
d < self.tol
})
.map(|s| {
x_pert
.iter()
.zip(s.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt()
})
.fold(f64::INFINITY, f64::min);
if d_x_finv.is_finite() {
let ratio = d_x_finv / d_y_fx;
if ratio > max_ratio {
max_ratio = ratio;
}
}
}
max_ratio
}
pub fn check_constraint_qualification(
constraints: &[impl Fn(&[f64]) -> f64],
x: &[f64],
h: f64,
) -> bool {
if constraints.is_empty() {
return true;
}
let mut grads: Vec<Vec<f64>> = Vec::new();
for c in constraints {
let cx = c(x);
if cx.abs() < h {
let g = clarke_gradient_approx(|pt| c(pt), x, h);
grads.push(g);
}
}
if grads.is_empty() {
return true;
}
for i in 0..grads.len() {
for j in (i + 1)..grads.len() {
let ni: f64 = grads[i].iter().map(|v| v * v).sum::<f64>().sqrt();
let nj: f64 = grads[j].iter().map(|v| v * v).sum::<f64>().sqrt();
if ni < 1e-12 || nj < 1e-12 {
return false;
}
let dot: f64 = grads[i]
.iter()
.zip(grads[j].iter())
.map(|(a, b)| a * b)
.sum();
let cos = (dot / (ni * nj)).abs();
if cos > 1.0 - 1e-6 {
return false;
}
}
}
true
}
pub fn check_quasiconvex(
f: impl Fn(&[f64]) -> f64,
x: &[f64],
y: &[f64],
num_samples: usize,
) -> bool {
let max_val = f(x).max(f(y));
for k in 1..num_samples {
let t = k as f64 / num_samples as f64;
let z: Vec<f64> = x
.iter()
.zip(y.iter())
.map(|(xi, yi)| (1.0 - t) * xi + t * yi)
.collect();
if f(&z) > max_val + 1e-12 {
return false;
}
}
true
}
}