use crate::geometry::iot::{IOTMetric, IOTCoordinates, IOTError};
use crate::core::FactorizationStateSpace;
use std::f64::consts::PI;
use std::fmt;
use thiserror::Error;
use serde::{Deserialize, Serialize};
#[derive(Error, Debug, Clone, PartialEq)]
pub enum TautochroneError {
#[error("IOT error: {0}")]
IOTError(#[from] IOTError),
#[error("Invalid path parameters: {0}")]
InvalidPath(String),
#[error("Geodesic computation error: {0}")]
GeodesicError(String),
#[error("Evolution error: {0}")]
EvolutionError(String),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TautochronePath {
start: IOTCoordinates,
end: IOTCoordinates,
parameter_range: (f64, f64),
coefficients: Vec<f64>,
}
impl TautochronePath {
pub fn new(start: IOTCoordinates, end: IOTCoordinates) -> Self {
TautochronePath {
start,
end,
parameter_range: (0.0, 1.0),
coefficients: vec![0.0; 6], }
}
pub fn coordinates_at(&self, t: f64) -> Result<IOTCoordinates, TautochroneError> {
if t < 0.0 || t > 1.0 {
return Err(TautochroneError::InvalidPath(
format!("Parameter t = {} not in [0, 1]", t)
));
}
let phi = self.start.phi + t * (self.end.phi - self.start.phi);
let theta = self.start.theta + t * (self.end.theta - self.start.theta);
let psi = self.start.psi + t * (self.end.psi - self.start.psi);
Ok(IOTCoordinates::new(phi, theta, psi)?)
}
pub fn length(&self, metric: &IOTMetric) -> f64 {
let num_segments = 100;
let dt = 1.0 / num_segments as f64;
let mut total_length = 0.0;
for i in 0..num_segments {
let t1 = i as f64 * dt;
let t2 = (i + 1) as f64 * dt;
if let (Ok(p1), Ok(p2)) = (self.coordinates_at(t1), self.coordinates_at(t2)) {
total_length += metric.geodesic_distance(&p1, &p2);
}
}
total_length
}
pub fn start(&self) -> &IOTCoordinates {
&self.start
}
pub fn end(&self) -> &IOTCoordinates {
&self.end
}
}
#[derive(Debug, Clone)]
pub struct TautochroneOperator {
metric: IOTMetric,
evolution_time: f64,
quantum_coupling: f64,
}
impl TautochroneOperator {
pub fn new(metric: IOTMetric) -> Self {
TautochroneOperator {
metric,
evolution_time: 1.0,
quantum_coupling: 1.0,
}
}
pub fn with_parameters(metric: IOTMetric, evolution_time: f64, quantum_coupling: f64) -> Self {
TautochroneOperator {
metric,
evolution_time,
quantum_coupling,
}
}
pub fn geodesic(&self, start: &IOTCoordinates, end: &IOTCoordinates) -> Result<TautochronePath, TautochroneError> {
let mut path = TautochronePath::new(start.clone(), end.clone());
self.compute_geodesic_coefficients(&mut path)?;
Ok(path)
}
fn compute_geodesic_coefficients(&self, path: &mut TautochronePath) -> Result<(), TautochroneError> {
let start = &path.start;
let end = &path.end;
path.coefficients[0] = end.phi - start.phi;
path.coefficients[1] = end.theta - start.theta;
path.coefficients[2] = end.psi - start.psi;
let mid_point = path.coordinates_at(0.5)?;
let christoffel = self.metric.christoffel_symbols(&mid_point);
path.coefficients[3] = christoffel.gamma_phi_phi_theta * 0.1;
path.coefficients[4] = christoffel.gamma_theta_phi_phi * 0.1;
path.coefficients[5] = christoffel.gamma_psi_theta_theta * 0.1;
Ok(())
}
pub fn tautochrone_time(&self, _path: &TautochronePath) -> f64 {
let major_r = self.metric.major_radius();
let g = self.quantum_coupling;
2.0 * PI * (major_r / g).sqrt()
}
pub fn evolve(&self, start: &IOTCoordinates, direction: &IOTCoordinates, time: f64) -> Result<IOTCoordinates, TautochroneError> {
let phi = start.phi + direction.phi * time * self.evolution_time;
let theta = start.theta + direction.theta * time * self.evolution_time;
let psi = start.psi + direction.psi * time * self.evolution_time;
let mut result = IOTCoordinates::new(phi, theta, psi)?;
result.normalize();
Ok(result)
}
pub fn parallel_transport(&self, path: &TautochronePath, vector: &IOTCoordinates) -> Result<IOTCoordinates, TautochroneError> {
let start_christoffel = self.metric.christoffel_symbols(&path.start);
let end_christoffel = self.metric.christoffel_symbols(&path.end);
let avg_gamma_phi = (start_christoffel.gamma_phi_phi_theta + end_christoffel.gamma_phi_phi_theta) / 2.0;
let avg_gamma_theta = (start_christoffel.gamma_theta_phi_phi + end_christoffel.gamma_theta_phi_phi) / 2.0;
let avg_gamma_psi = (start_christoffel.gamma_psi_theta_theta + end_christoffel.gamma_psi_theta_theta) / 2.0;
let phi_transported = vector.phi - avg_gamma_phi * vector.theta * 0.1;
let theta_transported = vector.theta - avg_gamma_theta * vector.phi * 0.1;
let psi_transported = vector.psi - avg_gamma_psi * vector.theta * 0.1;
Ok(IOTCoordinates::new(phi_transported, theta_transported, psi_transported)?)
}
pub fn action_functional(&self, path: &TautochronePath) -> f64 {
let num_segments = 50;
let dt = 1.0 / num_segments as f64;
let mut action = 0.0;
for i in 0..num_segments {
let t = i as f64 * dt;
if let Ok(coords) = path.coordinates_at(t) {
let metric_tensor = self.metric.metric_tensor(&coords);
let t_next = (i + 1) as f64 * dt;
if let Ok(coords_next) = path.coordinates_at(t_next) {
let dphi_dt = (coords_next.phi - coords.phi) / dt;
let dtheta_dt = (coords_next.theta - coords.theta) / dt;
let dpsi_dt = (coords_next.psi - coords.psi) / dt;
let kinetic = 0.5 * (
metric_tensor.g_phi_phi * dphi_dt * dphi_dt +
metric_tensor.g_theta_theta * dtheta_dt * dtheta_dt +
metric_tensor.g_psi_psi * dpsi_dt * dpsi_dt +
2.0 * metric_tensor.g_phi_theta * dphi_dt * dtheta_dt +
2.0 * metric_tensor.g_phi_psi * dphi_dt * dpsi_dt +
2.0 * metric_tensor.g_theta_psi * dtheta_dt * dpsi_dt
);
action += kinetic * dt;
}
}
}
action
}
pub fn minimize_action(&self, start: &IOTCoordinates, end: &IOTCoordinates) -> Result<TautochronePath, TautochroneError> {
let mut best_path = self.geodesic(start, end)?;
let mut best_action = self.action_functional(&best_path);
for i in 1..10 {
let alpha = i as f64 / 10.0;
let intermediate = IOTCoordinates::new(
start.phi + alpha * (end.phi - start.phi),
start.theta + alpha * (end.theta - start.theta),
start.psi + alpha * (end.psi - start.psi),
)?;
let path1 = self.geodesic(start, &intermediate)?;
let path2 = self.geodesic(&intermediate, end)?;
let combined_action = self.action_functional(&path1) + self.action_functional(&path2);
if combined_action < best_action {
best_action = combined_action;
best_path = path1; }
}
Ok(best_path)
}
pub fn path_curvature(&self, path: &TautochronePath) -> Vec<f64> {
let num_points = 20;
let mut curvatures = Vec::new();
for i in 0..num_points {
let t = i as f64 / (num_points - 1) as f64;
if let Ok(coords) = path.coordinates_at(t) {
let ricci = self.metric.ricci_scalar(&coords);
curvatures.push(ricci);
}
}
curvatures
}
pub fn is_tautochrone(&self, path: &TautochronePath) -> bool {
let travel_time = self.tautochrone_time(path);
let expected_time = 2.0 * PI * (self.metric.major_radius() / self.quantum_coupling).sqrt();
(travel_time - expected_time).abs() < 0.1
}
pub fn factorization_geodesic(&self,
state_space: &FactorizationStateSpace,
from_idx: usize,
to_idx: usize
) -> Result<TautochronePath, TautochroneError> {
let factorizations = state_space.factorizations();
if from_idx >= factorizations.len() || to_idx >= factorizations.len() {
return Err(TautochroneError::InvalidPath(
"Factorization indices out of bounds".to_string()
));
}
let start_coords = self.metric.factorization_to_coordinates(&factorizations[from_idx]);
let end_coords = self.metric.factorization_to_coordinates(&factorizations[to_idx]);
self.geodesic(&start_coords, &end_coords)
}
pub fn quantum_amplitude(&self, path: &TautochronePath) -> Result<f64, TautochroneError> {
let action = self.action_functional(path);
let hbar = 1.0;
let phase = action / hbar;
Ok(phase.cos() * phase.cos() + phase.sin() * phase.sin())
}
pub fn metric(&self) -> &IOTMetric {
&self.metric
}
}
#[derive(Debug, Clone)]
pub struct GeodesicSolver {
operator: TautochroneOperator,
step_size: f64,
max_steps: usize,
}
impl GeodesicSolver {
pub fn new(operator: TautochroneOperator) -> Self {
GeodesicSolver {
operator,
step_size: 0.01,
max_steps: 1000,
}
}
pub fn solve(&self, start: &IOTCoordinates, initial_velocity: &IOTCoordinates) -> Result<Vec<IOTCoordinates>, TautochroneError> {
let mut trajectory = Vec::new();
let mut current_pos = start.clone();
let mut current_vel = initial_velocity.clone();
trajectory.push(current_pos.clone());
for _ in 0..self.max_steps {
let christoffel = self.operator.metric.christoffel_symbols(¤t_pos);
let accel_phi = -christoffel.gamma_phi_phi_theta * current_vel.phi * current_vel.theta;
let accel_theta = -christoffel.gamma_theta_phi_phi * current_vel.phi * current_vel.phi;
let accel_psi = -christoffel.gamma_psi_theta_theta * current_vel.theta * current_vel.theta;
current_vel.phi += accel_phi * self.step_size;
current_vel.theta += accel_theta * self.step_size;
current_vel.psi += accel_psi * self.step_size;
current_pos.phi += current_vel.phi * self.step_size;
current_pos.theta += current_vel.theta * self.step_size;
current_pos.psi += current_vel.psi * self.step_size;
current_pos.normalize();
trajectory.push(current_pos.clone());
if current_pos.phi.is_nan() || current_pos.theta.is_nan() || current_pos.psi.is_nan() {
break;
}
}
Ok(trajectory)
}
}
impl fmt::Display for TautochronePath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Tautochrone path: {} → {}", self.start, self.end)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::iot::IOTMetric;
#[test]
fn test_tautochrone_path_creation() {
let start = IOTCoordinates::new(0.0, 0.0, 0.0).unwrap();
let end = IOTCoordinates::new(PI / 2.0, PI / 4.0, 0.5).unwrap();
let path = TautochronePath::new(start.clone(), end.clone());
assert_eq!(path.start(), &start);
assert_eq!(path.end(), &end);
}
#[test]
fn test_path_interpolation() {
let start = IOTCoordinates::new(0.0, 0.0, 0.0).unwrap();
let end = IOTCoordinates::new(PI, PI / 2.0, 1.0).unwrap();
let path = TautochronePath::new(start, end);
let mid_point = path.coordinates_at(0.5).unwrap();
assert!((mid_point.phi - PI / 2.0).abs() < 1e-10);
assert!((mid_point.theta - PI / 4.0).abs() < 1e-10);
assert!((mid_point.psi - 0.5).abs() < 1e-10);
assert!(path.coordinates_at(-0.1).is_err());
assert!(path.coordinates_at(1.1).is_err());
}
#[test]
fn test_tautochrone_operator() {
let metric = IOTMetric::new();
let operator = TautochroneOperator::new(metric);
let start = IOTCoordinates::new(0.0, 0.0, 0.0).unwrap();
let end = IOTCoordinates::new(PI / 4.0, PI / 4.0, 0.1).unwrap();
let path = operator.geodesic(&start, &end).unwrap();
assert_eq!(path.start(), &start);
assert_eq!(path.end(), &end);
}
#[test]
fn test_tautochrone_time() {
let metric = IOTMetric::new();
let operator = TautochroneOperator::new(metric);
let start = IOTCoordinates::new(0.0, 0.0, 0.0).unwrap();
let end = IOTCoordinates::new(PI / 2.0, PI / 2.0, 0.0).unwrap();
let path = TautochronePath::new(start, end);
let time = operator.tautochrone_time(&path);
assert!(time > 0.0);
assert!(time.is_finite());
assert!((time - 2.0 * PI).abs() < 1.0);
}
#[test]
fn test_evolution() {
let metric = IOTMetric::new();
let operator = TautochroneOperator::new(metric);
let start = IOTCoordinates::new(0.0, 0.0, 0.0).unwrap();
let direction = IOTCoordinates::new(0.1, 0.2, 0.05).unwrap();
let evolved = operator.evolve(&start, &direction, 0.5).unwrap();
assert!(evolved.phi > start.phi);
assert!(evolved.theta > start.theta);
assert!(evolved.psi > start.psi);
}
#[test]
fn test_parallel_transport() {
let metric = IOTMetric::new();
let operator = TautochroneOperator::new(metric);
let start = IOTCoordinates::new(0.0, 0.0, 0.0).unwrap();
let end = IOTCoordinates::new(PI / 4.0, PI / 4.0, 0.1).unwrap();
let path = TautochronePath::new(start, end);
let vector = IOTCoordinates::new(0.1, 0.2, 0.05).unwrap();
let transported = operator.parallel_transport(&path, &vector).unwrap();
assert!(transported.phi != vector.phi || transported.theta != vector.theta || transported.psi != vector.psi);
}
#[test]
fn test_action_functional() {
let metric = IOTMetric::new();
let operator = TautochroneOperator::new(metric);
let start = IOTCoordinates::new(0.0, 0.0, 0.0).unwrap();
let end = IOTCoordinates::new(PI / 6.0, PI / 6.0, 0.1).unwrap();
let path = TautochronePath::new(start, end);
let action = operator.action_functional(&path);
assert!(action > 0.0);
assert!(action.is_finite());
}
#[test]
fn test_path_curvature() {
let metric = IOTMetric::new();
let operator = TautochroneOperator::new(metric);
let start = IOTCoordinates::new(0.0, 0.0, 0.0).unwrap();
let end = IOTCoordinates::new(PI / 4.0, PI / 4.0, 0.1).unwrap();
let path = TautochronePath::new(start, end);
let curvatures = operator.path_curvature(&path);
assert!(!curvatures.is_empty());
assert!(curvatures.iter().all(|&c| c.is_finite()));
}
#[test]
fn test_quantum_amplitude() {
let metric = IOTMetric::new();
let operator = TautochroneOperator::new(metric);
let start = IOTCoordinates::new(0.0, 0.0, 0.0).unwrap();
let end = IOTCoordinates::new(PI / 8.0, PI / 8.0, 0.05).unwrap();
let path = TautochronePath::new(start, end);
let amplitude = operator.quantum_amplitude(&path).unwrap();
assert!(amplitude >= 0.0);
assert!(amplitude <= 1.0);
}
#[test]
fn test_factorization_geodesic() {
let state_space = FactorizationStateSpace::new(6).unwrap();
let metric = IOTMetric::from_state_space(state_space.clone());
let operator = TautochroneOperator::new(metric);
let path = operator.factorization_geodesic(&state_space, 0, 1).unwrap();
assert!(path.length(operator.metric()) > 0.0);
assert!(operator.factorization_geodesic(&state_space, 0, 10).is_err());
}
#[test]
fn test_geodesic_solver() {
let metric = IOTMetric::new();
let operator = TautochroneOperator::new(metric);
let solver = GeodesicSolver::new(operator);
let start = IOTCoordinates::new(0.0, 0.0, 0.0).unwrap();
let velocity = IOTCoordinates::new(0.1, 0.1, 0.01).unwrap();
let trajectory = solver.solve(&start, &velocity).unwrap();
assert!(trajectory.len() > 1);
assert_eq!(trajectory[0], start);
for i in 1..trajectory.len().min(10) {
let distance = solver.operator.metric.geodesic_distance(&trajectory[i-1], &trajectory[i]);
assert!(distance < 1.0); }
}
#[test]
fn test_minimize_action() {
let metric = IOTMetric::new();
let operator = TautochroneOperator::new(metric);
let start = IOTCoordinates::new(0.0, 0.0, 0.0).unwrap();
let end = IOTCoordinates::new(PI / 6.0, PI / 6.0, 0.1).unwrap();
let optimal_path = operator.minimize_action(&start, &end).unwrap();
let direct_path = operator.geodesic(&start, &end).unwrap();
let optimal_action = operator.action_functional(&optimal_path);
let direct_action = operator.action_functional(&direct_path);
assert!(optimal_action <= direct_action + 1e-10);
}
#[test]
fn test_is_tautochrone() {
let metric = IOTMetric::new();
let operator = TautochroneOperator::new(metric);
let start = IOTCoordinates::new(0.0, 0.0, 0.0).unwrap();
let end = IOTCoordinates::new(PI / 4.0, PI / 4.0, 0.0).unwrap();
let path = TautochronePath::new(start, end);
let is_tauto = operator.is_tautochrone(&path);
assert!(is_tauto); }
#[test]
fn test_display() {
let start = IOTCoordinates::new(0.0, 0.0, 0.0).unwrap();
let end = IOTCoordinates::new(1.0, 1.0, 0.5).unwrap();
let path = TautochronePath::new(start, end);
let path_str = format!("{}", path);
assert!(path_str.contains("Tautochrone path"));
assert!(path_str.contains("→"));
}
}