1pub mod geometry;
9
10use ry_core::{ModuleError, ModuleResult, RyditModule};
11use serde_json::{json, Value};
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).clamp(0.0, 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:
103 "bezier::quadratic requires 7 params: p0_x, p0_y, p1_x, p1_y, p2_x, p2_y, t"
104 .to_string(),
105 });
106 }
107
108 let p0_x = arr[0].as_f64().unwrap_or(0.0);
109 let p0_y = arr[1].as_f64().unwrap_or(0.0);
110 let p1_x = arr[2].as_f64().unwrap_or(0.0);
111 let p1_y = arr[3].as_f64().unwrap_or(0.0);
112 let p2_x = arr[4].as_f64().unwrap_or(0.0);
113 let p2_y = arr[5].as_f64().unwrap_or(0.0);
114 let t = arr[6].as_f64().unwrap_or(0.0).clamp(0.0, 1.0);
115
116 let mt = 1.0 - t;
117 let x = mt * mt * p0_x + 2.0 * mt * t * p1_x + t * t * p2_x;
118 let y = mt * mt * p0_y + 2.0 * mt * t * p1_y + t * t * p2_y;
119
120 Ok(json!([x, y]))
121 }
122
123 fn bezier_cubic(&self, params: Value) -> ModuleResult {
125 let arr = params.as_array().ok_or_else(|| ModuleError {
126 code: "INVALID_PARAMS".to_string(),
127 message: "Params must be an array".to_string(),
128 })?;
129
130 if arr.len() != 9 {
131 return Err(ModuleError {
132 code: "INVALID_PARAMS".to_string(),
133 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(),
134 });
135 }
136
137 let p0_x = arr[0].as_f64().unwrap_or(0.0);
138 let p0_y = arr[1].as_f64().unwrap_or(0.0);
139 let p1_x = arr[2].as_f64().unwrap_or(0.0);
140 let p1_y = arr[3].as_f64().unwrap_or(0.0);
141 let p2_x = arr[4].as_f64().unwrap_or(0.0);
142 let p2_y = arr[5].as_f64().unwrap_or(0.0);
143 let p3_x = arr[6].as_f64().unwrap_or(0.0);
144 let p3_y = arr[7].as_f64().unwrap_or(0.0);
145 let t = arr[8].as_f64().unwrap_or(0.0).clamp(0.0, 1.0);
146
147 let mt = 1.0 - t;
148 let mt2 = mt * mt;
149 let t2 = t * t;
150
151 let x = mt2 * mt * p0_x + 3.0 * mt2 * t * p1_x + 3.0 * mt * t2 * p2_x + t2 * t * p3_x;
152 let y = mt2 * mt * p0_y + 3.0 * mt2 * t * p1_y + 3.0 * mt * t2 * p2_y + t2 * t * p3_y;
153
154 Ok(json!([x, y]))
155 }
156
157 fn stats_mean(&self, params: Value) -> ModuleResult {
159 let arr = params.as_array().ok_or_else(|| ModuleError {
160 code: "INVALID_PARAMS".to_string(),
161 message: "Params must be an array".to_string(),
162 })?;
163
164 if arr.is_empty() {
165 return Err(ModuleError {
166 code: "INVALID_PARAMS".to_string(),
167 message: "Empty array".to_string(),
168 });
169 }
170
171 let sum: f64 = arr.iter().filter_map(|v| v.as_f64()).sum();
172 Ok(json!(sum / arr.len() as f64))
173 }
174
175 fn stats_median(&self, params: Value) -> ModuleResult {
177 let arr = params.as_array().ok_or_else(|| ModuleError {
178 code: "INVALID_PARAMS".to_string(),
179 message: "Params must be an array".to_string(),
180 })?;
181
182 let mut nums: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
183
184 if nums.is_empty() {
185 return Err(ModuleError {
186 code: "INVALID_PARAMS".to_string(),
187 message: "Empty array or no numbers".to_string(),
188 });
189 }
190
191 nums.sort_by(|a, b| a.partial_cmp(b).unwrap());
192 let mid = nums.len() / 2;
193
194 let median = if nums.len().is_multiple_of(2) {
195 (nums[mid - 1] + nums[mid]) / 2.0
196 } else {
197 nums[mid]
198 };
199
200 Ok(json!(median))
201 }
202
203 fn stats_min(&self, params: Value) -> ModuleResult {
205 let arr = params.as_array().ok_or_else(|| ModuleError {
206 code: "INVALID_PARAMS".to_string(),
207 message: "Params must be an array".to_string(),
208 })?;
209
210 let mut min_val = f64::MAX;
211 let mut found = false;
212
213 for v in arr {
214 if let Some(n) = v.as_f64() {
215 if n < min_val {
216 min_val = n;
217 }
218 found = true;
219 }
220 }
221
222 if found {
223 Ok(json!(min_val))
224 } else {
225 Err(ModuleError {
226 code: "INVALID_PARAMS".to_string(),
227 message: "No numbers in array".to_string(),
228 })
229 }
230 }
231
232 fn stats_max(&self, params: Value) -> ModuleResult {
234 let arr = params.as_array().ok_or_else(|| ModuleError {
235 code: "INVALID_PARAMS".to_string(),
236 message: "Params must be an array".to_string(),
237 })?;
238
239 let mut max_val = f64::MIN;
240 let mut found = false;
241
242 for v in arr {
243 if let Some(n) = v.as_f64() {
244 if n > max_val {
245 max_val = n;
246 }
247 found = true;
248 }
249 }
250
251 if found {
252 Ok(json!(max_val))
253 } else {
254 Err(ModuleError {
255 code: "INVALID_PARAMS".to_string(),
256 message: "No numbers in array".to_string(),
257 })
258 }
259 }
260
261 fn geometry_penrose(&self, params: Value) -> ModuleResult {
267 let arr = params.as_array().ok_or_else(|| ModuleError {
268 code: "INVALID_PARAMS".to_string(),
269 message: "Params must be an array".to_string(),
270 })?;
271
272 if arr.len() != 3 {
273 return Err(ModuleError {
274 code: "INVALID_PARAMS".to_string(),
275 message: "geometry::penrose requires 3 params: center_x, center_y, size"
276 .to_string(),
277 });
278 }
279
280 let center_x = arr[0].as_f64().unwrap_or(400.0);
281 let center_y = arr[1].as_f64().unwrap_or(300.0);
282 let size = arr[2].as_f64().unwrap_or(100.0);
283
284 Ok(geometry::penrose(center_x, center_y, size))
285 }
286
287 fn geometry_impossible_cube(&self, params: Value) -> ModuleResult {
289 let arr = params.as_array().ok_or_else(|| ModuleError {
290 code: "INVALID_PARAMS".to_string(),
291 message: "Params must be an array".to_string(),
292 })?;
293
294 if arr.len() != 3 {
295 return Err(ModuleError {
296 code: "INVALID_PARAMS".to_string(),
297 message: "geometry::impossible_cube requires 3 params: center_x, center_y, size"
298 .to_string(),
299 });
300 }
301
302 let center_x = arr[0].as_f64().unwrap_or(400.0);
303 let center_y = arr[1].as_f64().unwrap_or(300.0);
304 let size = arr[2].as_f64().unwrap_or(100.0);
305
306 Ok(geometry::impossible_cube(center_x, center_y, size))
307 }
308
309 fn geometry_spiral(&self, params: Value) -> ModuleResult {
311 let arr = params.as_array().ok_or_else(|| ModuleError {
312 code: "INVALID_PARAMS".to_string(),
313 message: "Params must be an array".to_string(),
314 })?;
315
316 if arr.len() != 5 {
317 return Err(ModuleError {
318 code: "INVALID_PARAMS".to_string(),
319 message:
320 "geometry::spiral requires 5 params: center_x, center_y, turns, radius, points"
321 .to_string(),
322 });
323 }
324
325 let center_x = arr[0].as_f64().unwrap_or(400.0);
326 let center_y = arr[1].as_f64().unwrap_or(300.0);
327 let turns = arr[2].as_i64().unwrap_or(3) as i32;
328 let radius = arr[3].as_f64().unwrap_or(100.0);
329 let points = arr[4].as_i64().unwrap_or(20) as i32;
330
331 Ok(geometry::spiral(center_x, center_y, turns, radius, points))
332 }
333
334 fn geometry_muller_lyer(&self, params: Value) -> ModuleResult {
336 let arr = params.as_array().ok_or_else(|| ModuleError {
337 code: "INVALID_PARAMS".to_string(),
338 message: "Params must be an array".to_string(),
339 })?;
340
341 if arr.len() != 3 {
342 return Err(ModuleError {
343 code: "INVALID_PARAMS".to_string(),
344 message: "geometry::muller_lyer requires 3 params: center_x, center_y, length"
345 .to_string(),
346 });
347 }
348
349 let center_x = arr[0].as_f64().unwrap_or(400.0);
350 let center_y = arr[1].as_f64().unwrap_or(300.0);
351 let length = arr[2].as_f64().unwrap_or(200.0);
352
353 Ok(geometry::muller_lyer(center_x, center_y, length))
354 }
355
356 fn geometry_ponzo(&self, params: Value) -> ModuleResult {
358 let arr = params.as_array().ok_or_else(|| ModuleError {
359 code: "INVALID_PARAMS".to_string(),
360 message: "Params must be an array".to_string(),
361 })?;
362
363 if arr.len() != 5 {
364 return Err(ModuleError {
365 code: "INVALID_PARAMS".to_string(),
366 message: "geometry::ponzo requires 5 params: center_x, center_y, height, width_top, width_bottom".to_string(),
367 });
368 }
369
370 let center_x = arr[0].as_f64().unwrap_or(400.0);
371 let center_y = arr[1].as_f64().unwrap_or(300.0);
372 let height = arr[2].as_f64().unwrap_or(300.0);
373 let width_top = arr[3].as_f64().unwrap_or(100.0);
374 let width_bottom = arr[4].as_f64().unwrap_or(300.0);
375
376 Ok(geometry::ponzo(
377 center_x,
378 center_y,
379 height,
380 width_top,
381 width_bottom,
382 ))
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389
390 #[test]
391 fn test_science_module_name() {
392 let module = ScienceModule;
393 assert_eq!(module.name(), "science");
394 assert_eq!(module.version(), "0.7.3");
395 }
396
397 #[test]
398 fn test_science_register() {
399 let module = ScienceModule;
400 let cmds = module.register();
401
402 assert!(cmds.contains_key("bezier::linear"));
403 assert!(cmds.contains_key("bezier::cubic"));
404 assert!(cmds.contains_key("stats::mean"));
405 assert!(cmds.contains_key("stats::median"));
406 assert!(cmds.contains_key("geometry::penrose"));
407 assert!(cmds.contains_key("geometry::impossible_cube"));
408 assert!(cmds.contains_key("geometry::spiral"));
409 }
410
411 #[test]
412 fn test_bezier_linear() {
413 let module = ScienceModule;
414 let params = json!([0.0, 0.0, 100.0, 100.0, 0.5]);
415 let result = module.execute("bezier::linear", params).unwrap();
416
417 assert_eq!(result, json!([50.0, 50.0]));
418 }
419
420 #[test]
421 fn test_bezier_cubic() {
422 let module = ScienceModule;
423 let params = json!([0.0, 0.0, 30.0, 100.0, 70.0, 100.0, 100.0, 0.0, 0.5]);
425 let result = module.execute("bezier::cubic", params).unwrap();
426
427 assert_eq!(result, json!([50.0, 75.0]));
428 }
429
430 #[test]
431 fn test_stats_mean() {
432 let module = ScienceModule;
433 let params = json!([1.0, 2.0, 3.0, 4.0, 5.0]);
434 let result = module.execute("stats::mean", params).unwrap();
435
436 assert_eq!(result, json!(3.0));
437 }
438
439 #[test]
440 fn test_stats_median_odd() {
441 let module = ScienceModule;
442 let params = json!([1.0, 2.0, 3.0, 4.0, 5.0]);
443 let result = module.execute("stats::median", params).unwrap();
444
445 assert_eq!(result, json!(3.0));
446 }
447
448 #[test]
449 fn test_stats_median_even() {
450 let module = ScienceModule;
451 let params = json!([1.0, 2.0, 3.0, 4.0]);
452 let result = module.execute("stats::median", params).unwrap();
453
454 assert_eq!(result, json!(2.5));
455 }
456
457 #[test]
458 fn test_stats_min_max() {
459 let module = ScienceModule;
460 let params = json!([3.0, 1.0, 4.0, 1.0, 5.0]);
461
462 let min_result = module.execute("stats::min", params.clone()).unwrap();
463 assert_eq!(min_result, json!(1.0));
464
465 let max_result = module.execute("stats::max", params).unwrap();
466 assert_eq!(max_result, json!(5.0));
467 }
468
469 #[test]
470 fn test_unknown_command() {
471 let module = ScienceModule;
472 let result = module.execute("unknown", json!([]));
473
474 assert!(result.is_err());
475 let err = result.unwrap_err();
476 assert_eq!(err.code, "UNKNOWN_COMMAND");
477 }
478
479 #[test]
481 fn test_geometry_penrose() {
482 let module = ScienceModule;
483 let params = json!([400.0, 300.0, 100.0]);
484 let result = module.execute("geometry::penrose", params).unwrap();
485
486 let lines = result.as_array().unwrap();
487 assert!(!lines.is_empty());
488 assert!(lines.len() >= 10);
489 }
490
491 #[test]
492 fn test_geometry_impossible_cube() {
493 let module = ScienceModule;
494 let params = json!([400.0, 300.0, 100.0]);
495 let result = module.execute("geometry::impossible_cube", params).unwrap();
496
497 let lines = result.as_array().unwrap();
498 assert!(!lines.is_empty());
499 assert!(lines.len() >= 12);
500 }
501
502 #[test]
503 fn test_geometry_spiral() {
504 let module = ScienceModule;
505 let params = json!([400.0, 300.0, 3, 100.0, 20]);
506 let result = module.execute("geometry::spiral", params).unwrap();
507
508 let points = result.as_array().unwrap();
509 assert_eq!(points.len(), 60); }
511
512 #[test]
513 fn test_geometry_muller_lyer() {
514 let module = ScienceModule;
515 let params = json!([400.0, 300.0, 200.0]);
516 let result = module.execute("geometry::muller_lyer", params).unwrap();
517
518 let lines = result.as_array().unwrap();
519 assert_eq!(lines.len(), 10);
520 }
521
522 #[test]
523 fn test_geometry_ponzo() {
524 let module = ScienceModule;
525 let params = json!([400.0, 300.0, 300.0, 100.0, 300.0]);
526 let result = module.execute("geometry::ponzo", params).unwrap();
527
528 let lines = result.as_array().unwrap();
529 assert_eq!(lines.len(), 6); }
531}