1pub mod geometry;
9
10use rydit_core::{RyditModule, ModuleResult, ModuleError};
11use serde_json::{Value, json};
12use std::collections::HashMap;
13
14pub struct ScienceModule;
16
17impl RyditModule for ScienceModule {
18 fn name(&self) -> &'static str {
19 "science"
20 }
21
22 fn version(&self) -> &'static str {
23 "0.7.3"
24 }
25
26 fn register(&self) -> HashMap<&'static str, &'static str> {
27 let mut cmds = HashMap::new();
28 cmds.insert("bezier::linear", "Curva Bezier lineal");
29 cmds.insert("bezier::quadratic", "Curva Bezier cuadrática");
30 cmds.insert("bezier::cubic", "Curva Bezier cúbica");
31 cmds.insert("stats::mean", "Media aritmética");
32 cmds.insert("stats::median", "Mediana");
33 cmds.insert("stats::min", "Valor mínimo");
34 cmds.insert("stats::max", "Valor máximo");
35 cmds.insert("geometry::penrose", "Triángulo de Penrose");
36 cmds.insert("geometry::impossible_cube", "Cubo imposible");
37 cmds.insert("geometry::spiral", "Espiral óptica");
38 cmds.insert("geometry::muller_lyer", "Ilusión Müller-Lyer");
39 cmds.insert("geometry::ponzo", "Ilusión de Ponzo");
40 cmds
41 }
42
43 fn execute(&self, command: &str, params: Value) -> ModuleResult {
44 match command {
45 "bezier::linear" => self.bezier_linear(params),
46 "bezier::quadratic" => self.bezier_quadratic(params),
47 "bezier::cubic" => self.bezier_cubic(params),
48 "stats::mean" => self.stats_mean(params),
49 "stats::median" => self.stats_median(params),
50 "stats::min" => self.stats_min(params),
51 "stats::max" => self.stats_max(params),
52 "geometry::penrose" => self.geometry_penrose(params),
53 "geometry::impossible_cube" => self.geometry_impossible_cube(params),
54 "geometry::spiral" => self.geometry_spiral(params),
55 "geometry::muller_lyer" => self.geometry_muller_lyer(params),
56 "geometry::ponzo" => self.geometry_ponzo(params),
57 _ => Err(ModuleError {
58 code: "UNKNOWN_COMMAND".to_string(),
59 message: format!("Comando desconocido: {}", command),
60 }),
61 }
62 }
63}
64
65impl ScienceModule {
66 fn bezier_linear(&self, params: Value) -> ModuleResult {
68 let arr = params.as_array().ok_or_else(|| ModuleError {
69 code: "INVALID_PARAMS".to_string(),
70 message: "Params must be an array".to_string(),
71 })?;
72
73 if arr.len() != 5 {
74 return Err(ModuleError {
75 code: "INVALID_PARAMS".to_string(),
76 message: "bezier::linear requires 5 params: p0_x, p0_y, p1_x, p1_y, t".to_string(),
77 });
78 }
79
80 let p0_x = arr[0].as_f64().unwrap_or(0.0);
81 let p0_y = arr[1].as_f64().unwrap_or(0.0);
82 let p1_x = arr[2].as_f64().unwrap_or(0.0);
83 let p1_y = arr[3].as_f64().unwrap_or(0.0);
84 let t = arr[4].as_f64().unwrap_or(0.0).max(0.0).min(1.0);
85
86 let x = (1.0 - t) * p0_x + t * p1_x;
87 let y = (1.0 - t) * p0_y + t * p1_y;
88
89 Ok(json!([x, y]))
90 }
91
92 fn bezier_quadratic(&self, params: Value) -> ModuleResult {
94 let arr = params.as_array().ok_or_else(|| ModuleError {
95 code: "INVALID_PARAMS".to_string(),
96 message: "Params must be an array".to_string(),
97 })?;
98
99 if arr.len() != 7 {
100 return Err(ModuleError {
101 code: "INVALID_PARAMS".to_string(),
102 message: "bezier::quadratic requires 7 params: p0_x, p0_y, p1_x, p1_y, p2_x, p2_y, t".to_string(),
103 });
104 }
105
106 let p0_x = arr[0].as_f64().unwrap_or(0.0);
107 let p0_y = arr[1].as_f64().unwrap_or(0.0);
108 let p1_x = arr[2].as_f64().unwrap_or(0.0);
109 let p1_y = arr[3].as_f64().unwrap_or(0.0);
110 let p2_x = arr[4].as_f64().unwrap_or(0.0);
111 let p2_y = arr[5].as_f64().unwrap_or(0.0);
112 let t = arr[6].as_f64().unwrap_or(0.0).max(0.0).min(1.0);
113
114 let mt = 1.0 - t;
115 let x = mt * mt * p0_x + 2.0 * mt * t * p1_x + t * t * p2_x;
116 let y = mt * mt * p0_y + 2.0 * mt * t * p1_y + t * t * p2_y;
117
118 Ok(json!([x, y]))
119 }
120
121 fn bezier_cubic(&self, params: Value) -> ModuleResult {
123 let arr = params.as_array().ok_or_else(|| ModuleError {
124 code: "INVALID_PARAMS".to_string(),
125 message: "Params must be an array".to_string(),
126 })?;
127
128 if arr.len() != 9 {
129 return Err(ModuleError {
130 code: "INVALID_PARAMS".to_string(),
131 message: "bezier::cubic requires 9 params: p0_x, p0_y, p1_x, p1_y, p2_x, p2_y, p3_x, p3_y, t".to_string(),
132 });
133 }
134
135 let p0_x = arr[0].as_f64().unwrap_or(0.0);
136 let p0_y = arr[1].as_f64().unwrap_or(0.0);
137 let p1_x = arr[2].as_f64().unwrap_or(0.0);
138 let p1_y = arr[3].as_f64().unwrap_or(0.0);
139 let p2_x = arr[4].as_f64().unwrap_or(0.0);
140 let p2_y = arr[5].as_f64().unwrap_or(0.0);
141 let p3_x = arr[6].as_f64().unwrap_or(0.0);
142 let p3_y = arr[7].as_f64().unwrap_or(0.0);
143 let t = arr[8].as_f64().unwrap_or(0.0).max(0.0).min(1.0);
144
145 let mt = 1.0 - t;
146 let mt2 = mt * mt;
147 let t2 = t * t;
148
149 let x = mt2 * mt * p0_x + 3.0 * mt2 * t * p1_x + 3.0 * mt * t2 * p2_x + t2 * t * p3_x;
150 let y = mt2 * mt * p0_y + 3.0 * mt2 * t * p1_y + 3.0 * mt * t2 * p2_y + t2 * t * p3_y;
151
152 Ok(json!([x, y]))
153 }
154
155 fn stats_mean(&self, params: Value) -> ModuleResult {
157 let arr = params.as_array().ok_or_else(|| ModuleError {
158 code: "INVALID_PARAMS".to_string(),
159 message: "Params must be an array".to_string(),
160 })?;
161
162 if arr.is_empty() {
163 return Err(ModuleError {
164 code: "INVALID_PARAMS".to_string(),
165 message: "Empty array".to_string(),
166 });
167 }
168
169 let sum: f64 = arr.iter().filter_map(|v| v.as_f64()).sum();
170 Ok(json!(sum / arr.len() as f64))
171 }
172
173 fn stats_median(&self, params: Value) -> ModuleResult {
175 let arr = params.as_array().ok_or_else(|| ModuleError {
176 code: "INVALID_PARAMS".to_string(),
177 message: "Params must be an array".to_string(),
178 })?;
179
180 let mut nums: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
181
182 if nums.is_empty() {
183 return Err(ModuleError {
184 code: "INVALID_PARAMS".to_string(),
185 message: "Empty array or no numbers".to_string(),
186 });
187 }
188
189 nums.sort_by(|a, b| a.partial_cmp(b).unwrap());
190 let mid = nums.len() / 2;
191
192 let median = if nums.len() % 2 == 0 {
193 (nums[mid - 1] + nums[mid]) / 2.0
194 } else {
195 nums[mid]
196 };
197
198 Ok(json!(median))
199 }
200
201 fn stats_min(&self, params: Value) -> ModuleResult {
203 let arr = params.as_array().ok_or_else(|| ModuleError {
204 code: "INVALID_PARAMS".to_string(),
205 message: "Params must be an array".to_string(),
206 })?;
207
208 let mut min_val = f64::MAX;
209 let mut found = false;
210
211 for v in arr {
212 if let Some(n) = v.as_f64() {
213 if n < min_val {
214 min_val = n;
215 }
216 found = true;
217 }
218 }
219
220 if found {
221 Ok(json!(min_val))
222 } else {
223 Err(ModuleError {
224 code: "INVALID_PARAMS".to_string(),
225 message: "No numbers in array".to_string(),
226 })
227 }
228 }
229
230 fn stats_max(&self, params: Value) -> ModuleResult {
232 let arr = params.as_array().ok_or_else(|| ModuleError {
233 code: "INVALID_PARAMS".to_string(),
234 message: "Params must be an array".to_string(),
235 })?;
236
237 let mut max_val = f64::MIN;
238 let mut found = false;
239
240 for v in arr {
241 if let Some(n) = v.as_f64() {
242 if n > max_val {
243 max_val = n;
244 }
245 found = true;
246 }
247 }
248
249 if found {
250 Ok(json!(max_val))
251 } else {
252 Err(ModuleError {
253 code: "INVALID_PARAMS".to_string(),
254 message: "No numbers in array".to_string(),
255 })
256 }
257 }
258
259 fn geometry_penrose(&self, params: Value) -> ModuleResult {
265 let arr = params.as_array().ok_or_else(|| ModuleError {
266 code: "INVALID_PARAMS".to_string(),
267 message: "Params must be an array".to_string(),
268 })?;
269
270 if arr.len() != 3 {
271 return Err(ModuleError {
272 code: "INVALID_PARAMS".to_string(),
273 message: "geometry::penrose requires 3 params: center_x, center_y, size".to_string(),
274 });
275 }
276
277 let center_x = arr[0].as_f64().unwrap_or(400.0);
278 let center_y = arr[1].as_f64().unwrap_or(300.0);
279 let size = arr[2].as_f64().unwrap_or(100.0);
280
281 Ok(geometry::penrose(center_x, center_y, size))
282 }
283
284 fn geometry_impossible_cube(&self, params: Value) -> ModuleResult {
286 let arr = params.as_array().ok_or_else(|| ModuleError {
287 code: "INVALID_PARAMS".to_string(),
288 message: "Params must be an array".to_string(),
289 })?;
290
291 if arr.len() != 3 {
292 return Err(ModuleError {
293 code: "INVALID_PARAMS".to_string(),
294 message: "geometry::impossible_cube requires 3 params: center_x, center_y, size".to_string(),
295 });
296 }
297
298 let center_x = arr[0].as_f64().unwrap_or(400.0);
299 let center_y = arr[1].as_f64().unwrap_or(300.0);
300 let size = arr[2].as_f64().unwrap_or(100.0);
301
302 Ok(geometry::impossible_cube(center_x, center_y, size))
303 }
304
305 fn geometry_spiral(&self, params: Value) -> ModuleResult {
307 let arr = params.as_array().ok_or_else(|| ModuleError {
308 code: "INVALID_PARAMS".to_string(),
309 message: "Params must be an array".to_string(),
310 })?;
311
312 if arr.len() != 5 {
313 return Err(ModuleError {
314 code: "INVALID_PARAMS".to_string(),
315 message: "geometry::spiral requires 5 params: center_x, center_y, turns, radius, points".to_string(),
316 });
317 }
318
319 let center_x = arr[0].as_f64().unwrap_or(400.0);
320 let center_y = arr[1].as_f64().unwrap_or(300.0);
321 let turns = arr[2].as_i64().unwrap_or(3) as i32;
322 let radius = arr[3].as_f64().unwrap_or(100.0);
323 let points = arr[4].as_i64().unwrap_or(20) as i32;
324
325 Ok(geometry::spiral(center_x, center_y, turns, radius, points))
326 }
327
328 fn geometry_muller_lyer(&self, params: Value) -> ModuleResult {
330 let arr = params.as_array().ok_or_else(|| ModuleError {
331 code: "INVALID_PARAMS".to_string(),
332 message: "Params must be an array".to_string(),
333 })?;
334
335 if arr.len() != 3 {
336 return Err(ModuleError {
337 code: "INVALID_PARAMS".to_string(),
338 message: "geometry::muller_lyer requires 3 params: center_x, center_y, length".to_string(),
339 });
340 }
341
342 let center_x = arr[0].as_f64().unwrap_or(400.0);
343 let center_y = arr[1].as_f64().unwrap_or(300.0);
344 let length = arr[2].as_f64().unwrap_or(200.0);
345
346 Ok(geometry::muller_lyer(center_x, center_y, length))
347 }
348
349 fn geometry_ponzo(&self, params: Value) -> ModuleResult {
351 let arr = params.as_array().ok_or_else(|| ModuleError {
352 code: "INVALID_PARAMS".to_string(),
353 message: "Params must be an array".to_string(),
354 })?;
355
356 if arr.len() != 5 {
357 return Err(ModuleError {
358 code: "INVALID_PARAMS".to_string(),
359 message: "geometry::ponzo requires 5 params: center_x, center_y, height, width_top, width_bottom".to_string(),
360 });
361 }
362
363 let center_x = arr[0].as_f64().unwrap_or(400.0);
364 let center_y = arr[1].as_f64().unwrap_or(300.0);
365 let height = arr[2].as_f64().unwrap_or(300.0);
366 let width_top = arr[3].as_f64().unwrap_or(100.0);
367 let width_bottom = arr[4].as_f64().unwrap_or(300.0);
368
369 Ok(geometry::ponzo(center_x, center_y, height, width_top, width_bottom))
370 }
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376
377 #[test]
378 fn test_science_module_name() {
379 let module = ScienceModule;
380 assert_eq!(module.name(), "science");
381 assert_eq!(module.version(), "0.7.3");
382 }
383
384 #[test]
385 fn test_science_register() {
386 let module = ScienceModule;
387 let cmds = module.register();
388
389 assert!(cmds.contains_key("bezier::linear"));
390 assert!(cmds.contains_key("bezier::cubic"));
391 assert!(cmds.contains_key("stats::mean"));
392 assert!(cmds.contains_key("stats::median"));
393 assert!(cmds.contains_key("geometry::penrose"));
394 assert!(cmds.contains_key("geometry::impossible_cube"));
395 assert!(cmds.contains_key("geometry::spiral"));
396 }
397
398 #[test]
399 fn test_bezier_linear() {
400 let module = ScienceModule;
401 let params = json!([0.0, 0.0, 100.0, 100.0, 0.5]);
402 let result = module.execute("bezier::linear", params).unwrap();
403
404 assert_eq!(result, json!([50.0, 50.0]));
405 }
406
407 #[test]
408 fn test_bezier_cubic() {
409 let module = ScienceModule;
410 let params = json!([0.0, 0.0, 30.0, 100.0, 70.0, 100.0, 100.0, 0.0, 0.5]);
412 let result = module.execute("bezier::cubic", params).unwrap();
413
414 assert_eq!(result, json!([50.0, 75.0]));
415 }
416
417 #[test]
418 fn test_stats_mean() {
419 let module = ScienceModule;
420 let params = json!([1.0, 2.0, 3.0, 4.0, 5.0]);
421 let result = module.execute("stats::mean", params).unwrap();
422
423 assert_eq!(result, json!(3.0));
424 }
425
426 #[test]
427 fn test_stats_median_odd() {
428 let module = ScienceModule;
429 let params = json!([1.0, 2.0, 3.0, 4.0, 5.0]);
430 let result = module.execute("stats::median", params).unwrap();
431
432 assert_eq!(result, json!(3.0));
433 }
434
435 #[test]
436 fn test_stats_median_even() {
437 let module = ScienceModule;
438 let params = json!([1.0, 2.0, 3.0, 4.0]);
439 let result = module.execute("stats::median", params).unwrap();
440
441 assert_eq!(result, json!(2.5));
442 }
443
444 #[test]
445 fn test_stats_min_max() {
446 let module = ScienceModule;
447 let params = json!([3.0, 1.0, 4.0, 1.0, 5.0]);
448
449 let min_result = module.execute("stats::min", params.clone()).unwrap();
450 assert_eq!(min_result, json!(1.0));
451
452 let max_result = module.execute("stats::max", params).unwrap();
453 assert_eq!(max_result, json!(5.0));
454 }
455
456 #[test]
457 fn test_unknown_command() {
458 let module = ScienceModule;
459 let result = module.execute("unknown", json!([]));
460
461 assert!(result.is_err());
462 let err = result.unwrap_err();
463 assert_eq!(err.code, "UNKNOWN_COMMAND");
464 }
465
466 #[test]
468 fn test_geometry_penrose() {
469 let module = ScienceModule;
470 let params = json!([400.0, 300.0, 100.0]);
471 let result = module.execute("geometry::penrose", params).unwrap();
472
473 let lines = result.as_array().unwrap();
474 assert!(!lines.is_empty());
475 assert!(lines.len() >= 10);
476 }
477
478 #[test]
479 fn test_geometry_impossible_cube() {
480 let module = ScienceModule;
481 let params = json!([400.0, 300.0, 100.0]);
482 let result = module.execute("geometry::impossible_cube", params).unwrap();
483
484 let lines = result.as_array().unwrap();
485 assert!(!lines.is_empty());
486 assert!(lines.len() >= 12);
487 }
488
489 #[test]
490 fn test_geometry_spiral() {
491 let module = ScienceModule;
492 let params = json!([400.0, 300.0, 3, 100.0, 20]);
493 let result = module.execute("geometry::spiral", params).unwrap();
494
495 let points = result.as_array().unwrap();
496 assert_eq!(points.len(), 60); }
498
499 #[test]
500 fn test_geometry_muller_lyer() {
501 let module = ScienceModule;
502 let params = json!([400.0, 300.0, 200.0]);
503 let result = module.execute("geometry::muller_lyer", params).unwrap();
504
505 let lines = result.as_array().unwrap();
506 assert_eq!(lines.len(), 10);
507 }
508
509 #[test]
510 fn test_geometry_ponzo() {
511 let module = ScienceModule;
512 let params = json!([400.0, 300.0, 300.0, 100.0, 300.0]);
513 let result = module.execute("geometry::ponzo", params).unwrap();
514
515 let lines = result.as_array().unwrap();
516 assert_eq!(lines.len(), 6); }
518}