//! NeuralOp Studio - Neural Operator Workbench. //! //! This demo showcases neural operators for solving PDEs, including //! FNO, DeepONet, and PINO implementations. pub mod deeponet; pub mod fno; pub mod pde_solver; pub mod pino; pub mod sample_data; use neuralop_studio_shared::{ EvaluationMetrics, OperatorConfig, OperatorType, PDEDefinition, TrainingConfig, TrainingProgress, TrainingResult, }; use thiserror::Error; /// Errors that can occur in NeuralOp Studio. #[derive(Debug, Error)] pub enum NeuralOpError { /// Invalid PDE configuration. #[error("Invalid PDE configuration: {0}")] InvalidPDE(String), /// Invalid operator configuration. #[error("Invalid operator configuration: {0}")] InvalidOperator(String), /// Training failed. #[error("Training failed: {0}")] TrainingFailed(String), /// Prediction failed. #[error("Prediction failed: {0}")] PredictionFailed(String), /// Unsupported feature. #[error("Unsupported feature: {0}")] Unsupported(String), } /// Neural operator trainer. pub trait NeuralOperatorTrainer: std::fmt::Debug { /// Train the neural operator. fn train( &mut self, pde: &PDEDefinition, config: &TrainingConfig, progress_callback: Option>, ) -> Result; /// Predict solution. fn predict(&self, input: &[f64], query_points: &[Vec]) -> Result, NeuralOpError>; /// Evaluate on test data. fn evaluate(&self, test_inputs: &[Vec], test_outputs: &[Vec]) -> EvaluationMetrics; /// Get number of parameters. fn num_parameters(&self) -> usize; /// Get operator type. fn operator_type(&self) -> OperatorType; } /// Main NeuralOp Studio system. #[derive(Debug)] pub struct NeuralOpStudio { /// Current operator. operator: Option>, /// Current PDE definition. pde: Option, /// Training history. training_history: Vec, } impl Default for NeuralOpStudio { fn default() -> Self { Self::new() } } impl NeuralOpStudio { /// Create a new NeuralOp Studio. #[must_use] pub fn new() -> Self { Self { operator: None, pde: None, training_history: vec![], } } /// Set the PDE to solve. pub fn set_pde(&mut self, pde: PDEDefinition) -> Result<(), NeuralOpError> { // Validate PDE if pde.domain.dimensions == 0 { return Err(NeuralOpError::InvalidPDE( "Domain must have at least 1 dimension".to_string(), )); } if pde.domain.resolution.len() != pde.domain.dimensions { return Err(NeuralOpError::InvalidPDE( "Resolution must match dimensions".to_string(), )); } self.pde = Some(pde); Ok(()) } /// Create an operator with the given configuration. pub fn create_operator(&mut self, config: OperatorConfig) -> Result<(), NeuralOpError> { let operator: Box = match config.operator_type { OperatorType::FNO => Box::new(fno::FourierNeuralOperator::new(config)), OperatorType::DeepONet => Box::new(deeponet::DeepONet::new(config)), OperatorType::PINO => Box::new(pino::PhysicsInformedNO::new(config)), OperatorType::SINO => { return Err(NeuralOpError::Unsupported( "SINO not yet implemented".to_string(), )); } OperatorType::GalerkinTransformer => { return Err(NeuralOpError::Unsupported( "Galerkin Transformer not yet implemented".to_string(), )); } OperatorType::MPNO => { return Err(NeuralOpError::Unsupported( "MPNO not yet implemented".to_string(), )); } }; self.operator = Some(operator); Ok(()) } /// Train the operator. pub fn train( &mut self, config: &TrainingConfig, progress_callback: Option>, ) -> Result { let pde = self .pde .as_ref() .ok_or_else(|| NeuralOpError::InvalidPDE("No PDE set".to_string()))?; let operator = self .operator .as_mut() .ok_or_else(|| NeuralOpError::InvalidOperator("No operator created".to_string()))?; let result = operator.train(pde, config, progress_callback)?; self.training_history.push(result.clone()); Ok(result) } /// Predict solution for given input. pub fn predict( &self, input: &[f64], query_points: &[Vec], ) -> Result, NeuralOpError> { let operator = self .operator .as_ref() .ok_or_else(|| NeuralOpError::InvalidOperator("No operator created".to_string()))?; operator.predict(input, query_points) } /// Get the current PDE. #[must_use] pub fn pde(&self) -> Option<&PDEDefinition> { self.pde.as_ref() } /// Get the training history. #[must_use] pub fn training_history(&self) -> &[TrainingResult] { &self.training_history } /// Get the current operator type. #[must_use] pub fn current_operator_type(&self) -> Option { self.operator.as_ref().map(|op| op.operator_type()) } } /// Run the full demo. pub fn run_demo() -> Result { use neuralop_studio_shared::{ sample_fno_config, sample_poisson_problem, sample_training_config, }; let mut studio = NeuralOpStudio::new(); // Set up problem studio.set_pde(sample_poisson_problem())?; studio.create_operator(sample_fno_config())?; // Train with a simple configuration let mut config = sample_training_config(); config.epochs = 10; // Quick demo config.num_train_samples = 100; config.num_val_samples = 20; config.num_test_samples = 20; // Train let result = studio.train(&config, None)?; Ok(result) } #[cfg(test)] mod tests { use super::*; use neuralop_studio_shared::{ sample_fno_config, sample_poisson_problem, sample_training_config, }; #[test] fn test_studio_creation() { let studio = NeuralOpStudio::new(); assert!(studio.pde().is_none()); assert!(studio.current_operator_type().is_none()); } #[test] fn test_set_pde() { let mut studio = NeuralOpStudio::new(); let pde = sample_poisson_problem(); assert!(studio.set_pde(pde).is_ok()); assert!(studio.pde().is_some()); } #[test] fn test_create_fno() { let mut studio = NeuralOpStudio::new(); let config = sample_fno_config(); assert!(studio.create_operator(config).is_ok()); assert_eq!(studio.current_operator_type(), Some(OperatorType::FNO)); } #[test] fn test_create_deeponet() { let mut studio = NeuralOpStudio::new(); let config = neuralop_studio_shared::sample_deeponet_config(); assert!(studio.create_operator(config).is_ok()); assert_eq!(studio.current_operator_type(), Some(OperatorType::DeepONet)); } #[test] fn test_create_pino() { let mut studio = NeuralOpStudio::new(); let config = neuralop_studio_shared::sample_pino_config(); assert!(studio.create_operator(config).is_ok()); assert_eq!(studio.current_operator_type(), Some(OperatorType::PINO)); } #[test] fn test_train() { let mut studio = NeuralOpStudio::new(); studio.set_pde(sample_poisson_problem()).unwrap(); studio.create_operator(sample_fno_config()).unwrap(); let mut config = sample_training_config(); config.epochs = 5; config.num_train_samples = 50; config.num_val_samples = 10; config.num_test_samples = 10; let result = studio.train(&config, None); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.final_train_loss > 0.0); assert_eq!(result.train_loss_history.len(), 5); } #[test] fn test_predict() { let mut studio = NeuralOpStudio::new(); studio.set_pde(sample_poisson_problem()).unwrap(); studio.create_operator(sample_fno_config()).unwrap(); let mut config = sample_training_config(); config.epochs = 2; config.num_train_samples = 20; config.num_val_samples = 5; config.num_test_samples = 5; studio.train(&config, None).unwrap(); let input = vec![1.0; 64 * 64]; // Dummy input let query_points: Vec> = (0..10) .map(|i| vec![i as f64 / 10.0, i as f64 / 10.0]) .collect(); let result = studio.predict(&input, &query_points); assert!(result.is_ok()); assert_eq!(result.unwrap().len(), 10); } #[test] fn test_run_demo() { let result = run_demo(); assert!(result.is_ok()); } #[test] fn test_unsupported_operators() { let mut studio = NeuralOpStudio::new(); let mut config = sample_fno_config(); config.operator_type = OperatorType::SINO; assert!(matches!( studio.create_operator(config), Err(NeuralOpError::Unsupported(_)) )); } }