Skip to main content

server_runner/
server_runner.rs

1use async_trait::async_trait;
2use ndarray::ArrayD;
3use std::collections::HashMap;
4use std::net::SocketAddr;
5use tonic::transport::Server;
6
7use philote_mdo::{
8    philote_info::{
9        discipline_service_server::DisciplineServiceServer,
10        explicit_service_server::ExplicitServiceServer, VariableMetaData, VariableType,
11    },
12    server::ExplicitServer,
13    traits::{Discipline, ExplicitDiscipline},
14    ArrayMap, PartialMap, PhiloteError, Result,
15};
16
17// Simple Paraboloid discipline for server example
18pub struct ParaboloidDiscipline {
19    variables: Vec<VariableMetaData>,
20    partials: Vec<(String, String)>,
21    options: HashMap<String, String>,
22}
23
24impl ParaboloidDiscipline {
25    pub fn new() -> Self {
26        Self {
27            variables: Vec::new(),
28            partials: Vec::new(),
29            options: HashMap::new(),
30        }
31    }
32}
33
34impl Discipline for ParaboloidDiscipline {
35    fn name(&self) -> &str {
36        "ParaboloidServer"
37    }
38
39    fn version(&self) -> &str {
40        "1.0.0"
41    }
42
43    fn is_continuous(&self) -> bool {
44        true
45    }
46
47    fn is_differentiable(&self) -> bool {
48        true
49    }
50
51    fn provides_gradients(&self) -> bool {
52        true
53    }
54
55    fn initialize(&mut self) -> Result<()> {
56        self.add_option("a", "double")?;
57        self.add_option("b", "double")?;
58        Ok(())
59    }
60
61    fn add_input(&mut self, name: &str, shape: &[usize], units: &str) -> Result<()> {
62        let var_meta = VariableMetaData {
63            r#type: VariableType::KInput as i32,
64            name: name.to_string(),
65            shape: shape.iter().map(|&s| s as i64).collect(),
66            units: units.to_string(),
67            dynamic_shape: false,
68        };
69        self.variables.push(var_meta);
70        Ok(())
71    }
72
73    fn add_output(&mut self, name: &str, shape: &[usize], units: &str) -> Result<()> {
74        let var_meta = VariableMetaData {
75            r#type: VariableType::KOutput as i32,
76            name: name.to_string(),
77            shape: shape.iter().map(|&s| s as i64).collect(),
78            units: units.to_string(),
79            dynamic_shape: false,
80        };
81        self.variables.push(var_meta);
82        Ok(())
83    }
84
85    fn add_option(&mut self, name: &str, option_type: &str) -> Result<()> {
86        self.options
87            .insert(name.to_string(), option_type.to_string());
88        Ok(())
89    }
90
91    fn set_options(&mut self, _options: &HashMap<String, serde_json::Value>) -> Result<()> {
92        Ok(())
93    }
94
95    fn setup(&mut self) -> Result<()> {
96        self.variables.clear();
97        self.add_input("x", &[1], "")?;
98        self.add_input("y", &[1], "")?;
99        self.add_output("f", &[1], "")?;
100        Ok(())
101    }
102
103    fn declare_partials(&mut self, func: &str, var: &str) -> Result<()> {
104        self.partials.push((func.to_string(), var.to_string()));
105        Ok(())
106    }
107
108    fn setup_partials(&mut self) -> Result<()> {
109        self.declare_partials("f", "x")?;
110        self.declare_partials("f", "y")?;
111        Ok(())
112    }
113
114    fn get_variable_definitions(&self) -> Result<Vec<VariableMetaData>> {
115        Ok(self.variables.clone())
116    }
117
118    fn get_partials_definitions(&self) -> Result<Vec<(String, String)>> {
119        Ok(self.partials.clone())
120    }
121
122    fn get_available_options(&self) -> Result<HashMap<String, String>> {
123        Ok(self.options.clone())
124    }
125}
126
127#[async_trait]
128impl ExplicitDiscipline for ParaboloidDiscipline {
129    async fn compute(&self, inputs: &ArrayMap) -> Result<ArrayMap> {
130        let x = inputs
131            .get("x")
132            .ok_or_else(|| PhiloteError::VariableNotFound("x".to_string()))?;
133
134        let y = inputs
135            .get("y")
136            .ok_or_else(|| PhiloteError::VariableNotFound("y".to_string()))?;
137
138        if x.len() != 1 || y.len() != 1 {
139            return Err(PhiloteError::array_error("Expected scalar inputs"));
140        }
141
142        let x_val = x[[0]];
143        let y_val = y[[0]];
144
145        // f = (x - 3)^2 + x*y + (y + 4)^2 - 3
146        let f_val = (x_val - 3.0).powi(2) + x_val * y_val + (y_val + 4.0).powi(2) - 3.0;
147
148        println!("🧮 Computing: x={}, y={} => f={}", x_val, y_val, f_val);
149
150        let mut outputs = HashMap::new();
151        let f_array = ArrayD::from_elem(vec![1], f_val);
152        outputs.insert("f".to_string(), f_array);
153
154        Ok(outputs)
155    }
156
157    async fn compute_partials(&self, inputs: &ArrayMap) -> Result<PartialMap> {
158        let x = inputs
159            .get("x")
160            .ok_or_else(|| PhiloteError::VariableNotFound("x".to_string()))?;
161
162        let y = inputs
163            .get("y")
164            .ok_or_else(|| PhiloteError::VariableNotFound("y".to_string()))?;
165
166        if x.len() != 1 || y.len() != 1 {
167            return Err(PhiloteError::array_error("Expected scalar inputs"));
168        }
169
170        let x_val = x[[0]];
171        let y_val = y[[0]];
172
173        // df/dx = 2*(x - 3) + y
174        let df_dx = 2.0 * (x_val - 3.0) + y_val;
175
176        // df/dy = x + 2*(y + 4)
177        let df_dy = x_val + 2.0 * (y_val + 4.0);
178
179        println!("📊 Computing gradients: df/dx={}, df/dy={}", df_dx, df_dy);
180
181        let mut partials = HashMap::new();
182        partials.insert(
183            ("f".to_string(), "x".to_string()),
184            ArrayD::from_elem(vec![1], df_dx),
185        );
186        partials.insert(
187            ("f".to_string(), "y".to_string()),
188            ArrayD::from_elem(vec![1], df_dy),
189        );
190
191        Ok(partials)
192    }
193}
194
195#[tokio::main]
196async fn main() -> Result<()> {
197    println!("🚀 Starting Philote Rust Server Example");
198
199    // Create and initialize the discipline
200    let mut discipline = ParaboloidDiscipline::new();
201    discipline.initialize()?;
202    discipline.setup()?;
203    discipline.setup_partials()?;
204
205    // Create the server
206    let server = ExplicitServer::new(discipline).with_verbose(true);
207
208    // Define the address
209    let addr: SocketAddr = "127.0.0.1:50051"
210        .parse()
211        .map_err(|e| PhiloteError::config_error(format!("Invalid address: {}", e)))?;
212
213    println!("🌐 Server listening on: {}", addr);
214    println!("📡 Ready to accept client connections!");
215    println!("💡 You can now run the client_example to test the connection.");
216
217    // Start the gRPC server with both ExplicitService and DisciplineService
218    // We need to use std::sync::Arc to share ownership between the two services
219    let server_arc = std::sync::Arc::new(server);
220
221    Server::builder()
222        .add_service(ExplicitServiceServer::from_arc(server_arc.clone()))
223        .add_service(DisciplineServiceServer::from_arc(server_arc))
224        .serve(addr)
225        .await
226        .map_err(|e| PhiloteError::config_error(format!("Server failed: {}", e)))?;
227
228    Ok(())
229}