use super::functions::*;
use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
#[derive(Debug, Clone)]
pub struct SecondVariation {
pub functional: String,
pub is_positive_definite: bool,
}
impl SecondVariation {
pub fn new(functional: impl Into<String>, is_positive_definite: bool) -> Self {
Self {
functional: functional.into(),
is_positive_definite,
}
}
pub fn legendre_condition(&self) -> bool {
self.is_positive_definite
}
pub fn jacobi_condition(&self) -> bool {
self.is_positive_definite
}
}
#[derive(Debug, Clone)]
pub struct ConservedQuantity {
pub name: String,
pub symmetry: Symmetry,
pub expression: String,
}
impl ConservedQuantity {
pub fn new(name: impl Into<String>, symmetry: Symmetry, expression: impl Into<String>) -> Self {
Self {
name: name.into(),
symmetry,
expression: expression.into(),
}
}
pub fn is_conserved_on_shell(&self) -> bool {
self.symmetry.is_continuous
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct MorseData {
pub critical_points: Vec<MorseCriticalPointData>,
}
impl MorseData {
pub fn new() -> Self {
Self::default()
}
pub fn add_critical_point(&mut self, cp: MorseCriticalPointData) {
self.critical_points.push(cp);
}
pub fn morse_count_by_index(&self) -> Vec<usize> {
let max_idx = self
.critical_points
.iter()
.map(|cp| cp.morse_index)
.max()
.unwrap_or(0);
let mut counts = vec![0usize; max_idx + 1];
for cp in &self.critical_points {
counts[cp.morse_index] += 1;
}
counts
}
pub fn euler_characteristic(&self) -> i64 {
self.critical_points
.iter()
.map(|cp| cp.euler_contribution())
.sum()
}
pub fn check_weak_morse_inequality(&self, betti: &[usize]) -> bool {
let counts = self.morse_count_by_index();
betti
.iter()
.enumerate()
.all(|(k, &b)| counts.get(k).copied().unwrap_or(0) >= b)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct NoetherSymmetryData {
pub name: String,
pub generator: Vec<String>,
pub conserved_current: Vec<String>,
pub conserved_charge: String,
pub is_gauge: bool,
}
impl NoetherSymmetryData {
pub fn new(name: &str, generator: Vec<String>, conserved_charge: &str, is_gauge: bool) -> Self {
Self {
name: name.to_string(),
generator,
conserved_current: Vec::new(),
conserved_charge: conserved_charge.to_string(),
is_gauge,
}
}
pub fn set_current(&mut self, current: Vec<String>) {
self.conserved_current = current;
}
pub fn is_on_shell_conserved(&self) -> bool {
!self.is_gauge
}
pub fn divergence_free_condition(&self) -> String {
let n = self.conserved_current.len();
if n == 0 {
return format!("div J = 0 [{}]", self.name);
}
let terms: Vec<String> = (0..n).map(|mu| format!("∂_{mu}(J^{mu})")).collect();
format!("{} = 0", terms.join(" + "))
}
pub fn energy_string(&self) -> String {
format!("E = ∫ T^{{00}} d^3x [symmetry: {}]", self.name)
}
}
#[derive(Debug, Clone)]
pub struct MinimalSurfaceFinder {
pub resolution: usize,
pub boundary: Vec<f64>,
}
impl MinimalSurfaceFinder {
pub fn new(resolution: usize) -> Self {
let n = resolution;
let mut boundary = vec![0.0_f64; 4 * n];
for k in 0..n {
let t = k as f64 / (n - 1) as f64;
let angle = t * std::f64::consts::PI * 0.5;
boundary[k] = angle.cos();
boundary[n + k] = angle.sin();
boundary[2 * n + k] = -(angle.cos());
boundary[3 * n + k] = -(angle.sin());
}
Self {
resolution,
boundary,
}
}
pub fn solve(&self, max_iter: usize, omega: f64) -> Vec<Vec<f64>> {
let n = self.resolution;
let ni = if n > 2 { n - 2 } else { 0 };
let mut u = vec![vec![0.0_f64; n]; n];
let bv = 1.0_f64;
for j in 0..n {
u[0][j] = bv;
u[n - 1][j] = bv;
}
for i in 0..n {
u[i][0] = bv;
u[i][n - 1] = bv;
}
for _ in 0..max_iter {
for i in 1..=ni {
for j in 1..=ni {
let avg = (u[i - 1][j] + u[i + 1][j] + u[i][j - 1] + u[i][j + 1]) / 4.0;
u[i][j] = (1.0 - omega) * u[i][j] + omega * avg;
}
}
}
u
}
pub fn dirichlet_energy(&self, u: &[Vec<f64>]) -> f64 {
let n = u.len();
if n < 2 {
return 0.0;
}
let mut energy = 0.0;
for i in 0..n {
for j in 0..n {
if i + 1 < n {
energy += (u[i + 1][j] - u[i][j]).powi(2);
}
if j + 1 < n {
energy += (u[i][j + 1] - u[i][j]).powi(2);
}
}
}
energy * 0.5
}
}
#[derive(Debug, Clone)]
pub struct ActionFunctional {
pub lagrangian: String,
pub time_start: f64,
pub time_end: f64,
}
impl ActionFunctional {
pub fn new(lagrangian: impl Into<String>, time_start: f64, time_end: f64) -> Self {
Self {
lagrangian: lagrangian.into(),
time_start,
time_end,
}
}
pub fn is_bounded_below(&self) -> bool {
self.time_end > self.time_start
}
}
#[derive(Debug, Clone)]
pub struct GeodesicOnSurface {
pub profile: Vec<(f64, f64)>,
pub start: (f64, f64),
pub end: (f64, f64),
pub n_nodes: usize,
}
impl GeodesicOnSurface {
pub fn new(
profile: Vec<(f64, f64)>,
start: (f64, f64),
end: (f64, f64),
n_nodes: usize,
) -> Self {
Self {
profile,
start,
end,
n_nodes,
}
}
pub fn r_at(&self, z: f64) -> f64 {
if self.profile.len() < 2 {
return 1.0;
}
let z0 = self.profile[0].0;
let z1 = self.profile[self.profile.len() - 1].0;
let t = (z - z0) / (z1 - z0).max(f64::EPSILON);
let t = t.clamp(0.0, 1.0);
let idx = ((t * (self.profile.len() - 1) as f64) as usize).min(self.profile.len() - 2);
let (za, ra) = self.profile[idx];
let (zb, rb) = self.profile[idx + 1];
let u = (z - za) / (zb - za).max(f64::EPSILON);
ra + u * (rb - ra)
}
pub fn solve(&self, max_iter: usize) -> Vec<(f64, f64)> {
let n = self.n_nodes;
let (z0, theta0) = self.start;
let (z1, theta1) = self.end;
let hz = (z1 - z0) / (n as f64 + 1.0);
let mut theta = vec![0.0_f64; n];
for i in 0..n {
let t = (i + 1) as f64 / (n as f64 + 1.0);
theta[i] = theta0 + t * (theta1 - theta0);
}
let tau = 1e-4_f64;
for _ in 0..max_iter {
let mut grad = vec![0.0_f64; n];
for i in 0..n {
let z = z0 + (i + 1) as f64 * hz;
let r = self.r_at(z);
let th_left = if i == 0 { theta0 } else { theta[i - 1] };
let th_right = if i == n - 1 { theta1 } else { theta[i + 1] };
let s_r = (th_right - theta[i]) / hz;
let s_l = (theta[i] - th_left) / hz;
let w = r * r;
grad[i] = -(w * s_r / (1.0 + w * s_r * s_r).sqrt()
- w * s_l / (1.0 + w * s_l * s_l).sqrt())
/ hz;
}
for i in 0..n {
theta[i] -= tau * grad[i];
}
}
let mut result = Vec::with_capacity(n + 2);
result.push((z0, theta0));
for i in 0..n {
result.push((z0 + (i + 1) as f64 * hz, theta[i]));
}
result.push((z1, theta1));
result
}
}
#[derive(Debug, Clone)]
pub struct OptimalTransportCost {
pub p: f64,
pub source: Vec<f64>,
pub target: Vec<f64>,
}
impl OptimalTransportCost {
pub fn new(p: f64, mut source: Vec<f64>, mut target: Vec<f64>) -> Self {
source.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
target.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
Self { p, source, target }
}
pub fn compute(&self) -> f64 {
let n = self.source.len().min(self.target.len());
if n == 0 {
return 0.0;
}
self.source[..n]
.iter()
.zip(self.target[..n].iter())
.map(|(x, y)| (x - y).abs().powf(self.p))
.sum::<f64>()
/ n as f64
}
pub fn w1_distance(&self) -> f64 {
let n = self.source.len().min(self.target.len());
if n == 0 {
return 0.0;
}
self.source[..n]
.iter()
.zip(self.target[..n].iter())
.map(|(x, y)| (x - y).abs())
.sum::<f64>()
/ n as f64
}
pub fn w2_distance(&self) -> f64 {
let n = self.source.len().min(self.target.len());
if n == 0 {
return 0.0;
}
let w2sq = self.source[..n]
.iter()
.zip(self.target[..n].iter())
.map(|(x, y)| (x - y).powi(2))
.sum::<f64>()
/ n as f64;
w2sq.sqrt()
}
}
#[derive(Debug, Clone)]
pub struct LagrangianFunction {
pub name: String,
pub vars: Vec<String>,
pub n_dof: usize,
}
impl LagrangianFunction {
pub fn new(n_dof: usize) -> Self {
let vars = (0..n_dof).map(|i| format!("q_{i}")).collect();
Self {
name: format!("L_{n_dof}dof"),
vars,
n_dof,
}
}
pub fn euler_lagrange_equation_string(&self) -> String {
let eqs: Vec<String> = (0..self.n_dof)
.map(|i| format!("d/dt(∂L/∂q̇_{i}) - ∂L/∂q_{i} = 0"))
.collect();
eqs.join("; ")
}
}
#[derive(Debug, Clone)]
pub struct EulerLagrangeSolver {
pub a: f64,
pub b: f64,
pub ya: f64,
pub yb: f64,
pub n_interior: usize,
pub step_size: f64,
}
impl EulerLagrangeSolver {
pub fn new(a: f64, b: f64, ya: f64, yb: f64, n_interior: usize, step_size: f64) -> Self {
Self {
a,
b,
ya,
yb,
n_interior,
step_size,
}
}
pub fn h(&self) -> f64 {
(self.b - self.a) / (self.n_interior as f64 + 1.0)
}
pub fn linear_initial_guess(&self) -> Vec<f64> {
let n = self.n_interior;
let h = self.h();
(1..=(n as u64))
.map(|k| {
let x = self.a + k as f64 * h;
let t = (x - self.a) / (self.b - self.a);
self.ya + t * (self.yb - self.ya)
})
.collect()
}
pub fn solve_arc_length(&self, max_iter: usize) -> Vec<f64> {
let n = self.n_interior;
let h = self.h();
let mut y = self.linear_initial_guess();
for _ in 0..max_iter {
let mut grad = vec![0.0_f64; n];
for i in 0..n {
let y_left = if i == 0 { self.ya } else { y[i - 1] };
let y_right = if i == n - 1 { self.yb } else { y[i + 1] };
let s_r = (y_right - y[i]) / h;
let s_l = (y[i] - y_left) / h;
grad[i] = -(s_r / (1.0 + s_r * s_r).sqrt() - s_l / (1.0 + s_l * s_l).sqrt()) / h;
}
for i in 0..n {
y[i] -= self.step_size * grad[i];
}
}
y
}
pub fn arc_length(&self, y_interior: &[f64]) -> f64 {
let h = self.h();
let n = y_interior.len();
let mut total = 0.0;
for i in 0..=n {
let y_left = if i == 0 { self.ya } else { y_interior[i - 1] };
let y_right = if i == n { self.yb } else { y_interior[i] };
let slope = (y_right - y_left) / h;
total += h * (1.0 + slope * slope).sqrt();
}
total
}
}
#[derive(Debug, Clone)]
pub struct MinimalSurfaceProblem {
pub boundary: String,
}
impl MinimalSurfaceProblem {
pub fn new(boundary: impl Into<String>) -> Self {
Self {
boundary: boundary.into(),
}
}
pub fn euler_lagrange_equation(&self) -> String {
"(1 + u_y^2) * u_xx - 2 * u_x * u_y * u_xy + (1 + u_x^2) * u_yy = 0 (H = 0)".to_string()
}
pub fn minimal_surface_area_lower_bound(&self) -> f64 {
0.0
}
}
#[derive(Debug, Clone)]
pub struct LowerSemicontinuous {
pub functional: String,
pub topology: String,
}
impl LowerSemicontinuous {
pub fn new(functional: impl Into<String>, topology: impl Into<String>) -> Self {
Self {
functional: functional.into(),
topology: topology.into(),
}
}
pub fn direct_method_applicable(&self) -> bool {
true
}
}
#[derive(Debug, Clone)]
pub struct WassersteinGradientFlow {
pub tau: f64,
pub positions: Vec<f64>,
pub n_particles: usize,
pub potential_alpha: f64,
}
impl WassersteinGradientFlow {
pub fn new(n_particles: usize, tau: f64, potential_alpha: f64) -> Self {
let positions: Vec<f64> = (0..n_particles)
.map(|i| {
let p = (i as f64 + 0.5) / n_particles as f64;
let t = (-2.0 * (p * (1.0 - p)).ln()).sqrt();
let c0 = 2.515517_f64;
let c1 = 0.802853_f64;
let c2 = 0.010328_f64;
let d1 = 1.432788_f64;
let d2 = 0.189269_f64;
let d3 = 0.001308_f64;
let num = c0 + c1 * t + c2 * t * t;
let den = 1.0 + d1 * t + d2 * t * t + d3 * t * t * t;
if p < 0.5 {
-(t - num / den)
} else {
t - num / den
}
})
.collect();
Self {
tau,
positions,
n_particles,
potential_alpha,
}
}
pub fn step(&mut self) {
let factor = 1.0 / (1.0 + self.tau * self.potential_alpha);
for x in self.positions.iter_mut() {
*x *= factor;
}
}
pub fn run(&mut self, n_steps: usize) {
for _ in 0..n_steps {
self.step();
}
}
pub fn mean(&self) -> f64 {
self.positions.iter().sum::<f64>() / self.n_particles as f64
}
pub fn variance(&self) -> f64 {
let mu = self.mean();
self.positions.iter().map(|x| (x - mu).powi(2)).sum::<f64>() / self.n_particles as f64
}
pub fn steady_state_variance(&self) -> f64 {
if self.potential_alpha.abs() < f64::EPSILON {
f64::INFINITY
} else {
1.0 / self.potential_alpha
}
}
}
#[derive(Debug, Clone)]
pub struct GeodesicEquation {
pub metric: String,
pub dimension: usize,
}
impl GeodesicEquation {
pub fn new(dimension: usize) -> Self {
Self {
metric: format!("g_{{ij}} on R^{dimension}"),
dimension,
}
}
pub fn christoffel_symbols_string(&self) -> String {
format!(
"Gamma^k_{{ij}} = (1/2) g^{{kl}} (partial_i g_{{jl}} + partial_j g_{{il}} - partial_l g_{{ij}}) \
for i,j,k,l in 1..{}",
self.dimension
)
}
pub fn is_straight_line_in_flat_space(&self) -> bool {
true
}
}
#[derive(Debug, Clone)]
pub struct BrachistochroneProblem {
pub start: (f64, f64),
pub end: (f64, f64),
}
impl BrachistochroneProblem {
pub fn new(start: (f64, f64), end: (f64, f64)) -> Self {
Self { start, end }
}
pub fn cycloid_solution(&self) -> String {
"x(theta) = R * (theta - sin(theta)), \
y(theta) = R * (1 - cos(theta)), \
where R is chosen so the cycloid passes through the endpoint."
.to_string()
}
pub fn time_on_cycloid(&self) -> f64 {
let dy = (self.end.1 - self.start.1).abs();
if dy < f64::EPSILON {
return 0.0;
}
let g = 9.81_f64;
let r = dy / 2.0;
std::f64::consts::PI * (r / g).sqrt()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SymmetryType {
TimeTranslation,
SpaceTranslation,
Rotation,
Boost,
Internal,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct YangMillsEnergy {
pub n: usize,
pub links: Vec<[f64; 2]>,
}
impl YangMillsEnergy {
pub fn new(n: usize) -> Self {
Self {
n,
links: vec![[0.0; 2]; n * n],
}
}
pub fn set_link(&mut self, i: usize, j: usize, dir: usize, theta: f64) {
if i < self.n && j < self.n && dir < 2 {
self.links[i * self.n + j][dir] = theta;
}
}
pub fn get_link(&self, i: usize, j: usize, dir: usize) -> f64 {
if i < self.n && j < self.n && dir < 2 {
self.links[i * self.n + j][dir]
} else {
0.0
}
}
pub fn plaquette_angle(&self, i: usize, j: usize) -> f64 {
let n = self.n;
let i1 = (i + 1) % n;
let j1 = (j + 1) % n;
let a = self.get_link(i, j, 0);
let b = self.get_link(i1, j, 1);
let c = self.get_link(i, j1, 0);
let d = self.get_link(i, j, 1);
a + b - c - d
}
pub fn total_energy(&self) -> f64 {
let n = self.n;
(0..n)
.flat_map(|i| (0..n).map(move |j| (i, j)))
.map(|(i, j)| 1.0 - self.plaquette_angle(i, j).cos())
.sum()
}
pub fn gradient_descent_step(&mut self, step_size: f64) {
let n = self.n;
let mut grad = vec![[0.0_f64; 2]; n * n];
for i in 0..n {
for j in 0..n {
let theta_p = self.plaquette_angle(i, j);
let sin_p = theta_p.sin();
let j_prev = if j == 0 { n - 1 } else { j - 1 };
let theta_p_prev = self.plaquette_angle(i, j_prev);
grad[i * n + j][0] += sin_p - theta_p_prev.sin();
let i_prev = if i == 0 { n - 1 } else { i - 1 };
let theta_p_iprev = self.plaquette_angle(i_prev, j);
grad[i * n + j][1] += sin_p - theta_p_iprev.sin();
}
}
for k in 0..(n * n) {
self.links[k][0] -= step_size * grad[k][0];
self.links[k][1] -= step_size * grad[k][1];
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct MorseCriticalPointData {
pub label: String,
pub critical_value: f64,
pub morse_index: usize,
pub non_degenerate: bool,
}
impl MorseCriticalPointData {
pub fn new(label: &str, critical_value: f64, morse_index: usize, non_degenerate: bool) -> Self {
Self {
label: label.to_string(),
critical_value,
morse_index,
non_degenerate,
}
}
pub fn is_local_minimum(&self) -> bool {
self.non_degenerate && self.morse_index == 0
}
pub fn is_saddle_point(&self) -> bool {
self.non_degenerate && self.morse_index > 0
}
pub fn euler_contribution(&self) -> i64 {
if self.morse_index % 2 == 0 {
1
} else {
-1
}
}
}
#[derive(Debug, Clone)]
pub struct FirstVariation {
pub functional: String,
pub direction: String,
}
impl FirstVariation {
pub fn new(functional: impl Into<String>, direction: impl Into<String>) -> Self {
Self {
functional: functional.into(),
direction: direction.into(),
}
}
pub fn vanishes_at_critical_point(&self) -> bool {
true
}
}
#[derive(Debug, Clone)]
pub struct SobolevSpace {
pub domain: String,
pub k: u32,
pub p: f64,
}
impl SobolevSpace {
pub fn new(domain: impl Into<String>, k: u32, p: f64) -> Self {
Self {
domain: domain.into(),
k,
p,
}
}
pub fn norm_formula(&self) -> String {
format!(
"||u||_{{W^{{{},{}}}({})}} = (sum_{{|alpha|<={}}} ||D^alpha u||_{{L^{}}}^{})^{{1/{}}}",
self.k, self.p as u32, self.domain, self.k, self.p as u32, self.p as u32, self.p as u32
)
}
pub fn is_hilbert_space(&self) -> bool {
self.k >= 1 && (self.p - 2.0).abs() < f64::EPSILON
}
}
#[derive(Debug, Clone)]
pub struct Symmetry {
pub name: String,
pub transformation_type: SymmetryType,
pub is_continuous: bool,
}
impl Symmetry {
pub fn new(
name: impl Into<String>,
transformation_type: SymmetryType,
is_continuous: bool,
) -> Self {
Self {
name: name.into(),
transformation_type,
is_continuous,
}
}
}
#[derive(Debug, Clone)]
pub struct NoetherCorrespondence {
pub symmetry: Symmetry,
pub conserved_charge: ConservedQuantity,
}
impl NoetherCorrespondence {
pub fn new(symmetry: Symmetry, conserved_charge: ConservedQuantity) -> Self {
Self {
symmetry,
conserved_charge,
}
}
pub fn noether_current(&self) -> String {
format!(
"j^mu = (∂L/∂(∂_mu phi)) * delta_phi [symmetry: {}]",
self.symmetry.name
)
}
}
#[derive(Debug, Clone)]
pub struct ControlSystem {
pub state_dim: usize,
pub control_dim: usize,
pub dynamics: String,
}
impl ControlSystem {
pub fn new(state_dim: usize, control_dim: usize) -> Self {
Self {
state_dim,
control_dim,
dynamics: format!("dx/dt = f(x in R^{state_dim}, u in R^{control_dim}, t)"),
}
}
pub fn is_controllable(&self) -> bool {
self.control_dim >= 1 && self.state_dim >= 1
}
pub fn is_observable(&self) -> bool {
self.state_dim >= 1
}
}
#[derive(Debug, Clone)]
pub struct IsoperimetricProblem {
pub constraint: String,
pub objective: String,
}
impl IsoperimetricProblem {
pub fn new(constraint: impl Into<String>, objective: impl Into<String>) -> Self {
Self {
constraint: constraint.into(),
objective: objective.into(),
}
}
pub fn lagrange_multiplier_method(&self) -> String {
format!(
"Extremize F[y] = {} subject to G[y] = {}. \
Form augmented functional H[y] = F[y] - lambda * G[y] \
and apply Euler-Lagrange to H.",
self.objective, self.constraint
)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct LyusternikSchnirelmannData {
pub ls_category: usize,
pub minimax_values: Vec<f64>,
pub critical_point_labels: Vec<String>,
}
impl LyusternikSchnirelmannData {
pub fn new(ls_category: usize) -> Self {
Self {
ls_category,
minimax_values: Vec::new(),
critical_point_labels: Vec::new(),
}
}
pub fn add_minimax_value(&mut self, value: f64, label: &str) {
self.minimax_values.push(value);
self.critical_point_labels.push(label.to_string());
}
pub fn all_critical_points_found(&self) -> bool {
self.minimax_values.len() >= self.ls_category
}
pub fn lower_bound_on_critical_points(&self) -> usize {
self.ls_category
}
pub fn is_ordered(&self) -> bool {
self.minimax_values.windows(2).all(|w| w[0] <= w[1] + 1e-12)
}
pub fn mountain_pass_value(&self) -> Option<f64> {
self.minimax_values.get(1).copied()
}
}
#[allow(non_snake_case)]
#[derive(Debug, Clone)]
pub struct HamiltonianMechanics {
pub n_dof: usize,
#[allow(non_snake_case)]
pub H: String,
}
impl HamiltonianMechanics {
pub fn new(n: usize) -> Self {
let coords: Vec<String> = (0..n).map(|i| format!("q_{i}")).collect();
let momenta: Vec<String> = (0..n).map(|i| format!("p_{i}")).collect();
let all_vars = [coords.as_slice(), momenta.as_slice()].concat().join(", ");
Self {
n_dof: n,
H: format!("H({all_vars}, t)"),
}
}
pub fn hamilton_equations_string(&self) -> Vec<String> {
(0..self.n_dof)
.flat_map(|i| {
vec![
format!("dq_{i}/dt = ∂H/∂p_{i}"),
format!("dp_{i}/dt = -∂H/∂q_{i}"),
]
})
.collect()
}
pub fn phase_space_dim(&self) -> usize {
2 * self.n_dof
}
}
#[derive(Debug, Clone)]
pub struct HamiltonJacobiBellman {
pub value_function: String,
pub dimension: usize,
}
impl HamiltonJacobiBellman {
pub fn new(dimension: usize) -> Self {
Self {
value_function: format!("V : R^{dimension} x [0,T] -> R"),
dimension,
}
}
pub fn verification_theorem(&self) -> String {
format!(
"If V : R^{dim} x [0,T] -> R is C^1 and satisfies \
-∂V/∂t = min_u H(x, u, ∇_x V, t) with V(x,T) = phi(x), \
then V is the optimal value function and u*(x,t) = argmin_u H is optimal.",
dim = self.dimension
)
}
}
#[derive(Debug, Clone)]
pub struct GammaLimit {
pub sequence_name: String,
pub limit_name: String,
}
impl GammaLimit {
pub fn new(sequence_name: impl Into<String>, limit_name: impl Into<String>) -> Self {
Self {
sequence_name: sequence_name.into(),
limit_name: limit_name.into(),
}
}
pub fn lsc_envelope(&self) -> String {
format!(
"Gamma-lim F_n = lsc envelope of (pointwise lim F_n) \
[sequence: {}, limit: {}]",
self.sequence_name, self.limit_name
)
}
pub fn recovery_sequence_exists(&self) -> bool {
true
}
}
#[derive(Debug, Clone)]
pub struct WeakConvergence {
pub sequence: String,
pub limit: String,
pub space: SobolevSpace,
}
impl WeakConvergence {
pub fn new(sequence: impl Into<String>, limit: impl Into<String>, space: SobolevSpace) -> Self {
Self {
sequence: sequence.into(),
limit: limit.into(),
space,
}
}
pub fn is_weakly_convergent(&self) -> bool {
self.space.p > 1.0
}
}
#[derive(Debug, Clone)]
pub struct PontryaginPrinciple {
pub system: ControlSystem,
pub cost: String,
}
impl PontryaginPrinciple {
pub fn new(system: ControlSystem, cost: impl Into<String>) -> Self {
Self {
system,
cost: cost.into(),
}
}
pub fn hamiltonian_string(&self) -> String {
format!(
"H(x, u, p, t) = L(x, u, t) + p^T * f(x, u, t) \
where p in R^{} is the costate (adjoint variable)",
self.system.state_dim
)
}
}
#[derive(Debug, Clone)]
pub struct Functional {
pub name: String,
pub domain: String,
pub codomain: String,
}
impl Functional {
pub fn new(
name: impl Into<String>,
domain: impl Into<String>,
codomain: impl Into<String>,
) -> Self {
Self {
name: name.into(),
domain: domain.into(),
codomain: codomain.into(),
}
}
}
#[derive(Debug, Clone)]
pub struct ELSolution {
pub lagrangian: LagrangianFunction,
pub solution: String,
pub is_extremal: bool,
}
impl ELSolution {
pub fn new() -> Self {
Self {
lagrangian: LagrangianFunction::new(1),
solution: "q(t) = q_0 + (q_1 - q_0) * t".to_string(),
is_extremal: true,
}
}
pub fn is_local_minimum(&self) -> bool {
self.is_extremal
}
pub fn is_local_maximum(&self) -> bool {
false
}
pub fn is_saddle(&self) -> bool {
!self.is_extremal
}
}
#[derive(Debug, Clone)]
pub struct LegendreTransform {
pub function: String,
pub variable: String,
pub conjugate: String,
}
impl LegendreTransform {
pub fn new(
function: impl Into<String>,
variable: impl Into<String>,
conjugate: impl Into<String>,
) -> Self {
Self {
function: function.into(),
variable: variable.into(),
conjugate: conjugate.into(),
}
}
pub fn is_involution(&self) -> bool {
true
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct PalaisSmaleChecker {
pub values: Vec<f64>,
pub gradient_norms: Vec<f64>,
pub step_sizes: Vec<f64>,
}
impl PalaisSmaleChecker {
pub fn new() -> Self {
Self::default()
}
pub fn record(&mut self, value: f64, grad_norm: f64, step_size: f64) {
self.values.push(value);
self.gradient_norms.push(grad_norm);
self.step_sizes.push(step_size);
}
pub fn values_bounded(&self, bound: f64) -> bool {
self.values.iter().all(|v| v.abs() <= bound)
}
pub fn gradients_converge_to_zero(&self, tol: f64) -> bool {
self.gradient_norms
.last()
.map(|&g| g < tol)
.unwrap_or(false)
}
pub fn satisfies_approximate_ps(&self, value_bound: f64, grad_tol: f64) -> bool {
if !self.values_bounded(value_bound) || !self.gradients_converge_to_zero(grad_tol) {
return false;
}
if self.step_sizes.len() < 2 {
return true;
}
self.step_sizes.windows(2).all(|w| w[1] <= w[0] + 1e-12)
}
pub fn min_gradient_norm(&self) -> f64 {
self.gradient_norms
.iter()
.cloned()
.fold(f64::INFINITY, f64::min)
}
}
#[derive(Debug, Clone)]
pub struct LagrangianMechanics {
pub n_dof: usize,
pub constraints: Vec<String>,
}
impl LagrangianMechanics {
pub fn new(n: usize) -> Self {
Self {
n_dof: n,
constraints: vec![],
}
}
pub fn kinetic_energy_string(&self) -> String {
let terms: Vec<String> = (0..self.n_dof)
.map(|i| format!("m_{i} * q̇_{i}^2"))
.collect();
format!("T = (1/2) * ({})", terms.join(" + "))
}
pub fn potential_energy_string(&self) -> String {
let args: Vec<String> = (0..self.n_dof).map(|i| format!("q_{i}")).collect();
format!("V = V({})", args.join(", "))
}
}