1use crate::error::{CoreError, ErrorContext, ErrorLocation};
2use ::ndarray::{Array, Dimension, IxDyn};
3use std::any::Any;
4use std::fmt;
5use std::ops::{Add, Div, Mul, Sub};
6use std::rc::Rc;
7
8#[derive(Clone, Debug)]
10pub enum LazyOpKind {
11 Unary,
13 Binary,
15 Reduce,
17 ElementWise,
19 Reshape,
21 Transpose,
23 AxisOp,
25}
26
27#[derive(Clone)]
29pub struct LazyOp {
30 pub kind: LazyOpKind,
32 pub op: Rc<dyn Any>,
34 pub data: Option<Rc<dyn Any>>,
36}
37
38impl fmt::Debug for LazyOp {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self.kind {
41 LazyOpKind::Unary => write!(f, "Unary Operation"),
42 LazyOpKind::Binary => write!(f, "Binary Operation"),
43 LazyOpKind::Reduce => write!(f, "Reduction Operation"),
44 LazyOpKind::ElementWise => write!(f, "Element-wise Operation"),
45 LazyOpKind::Reshape => write!(f, "Reshape Operation"),
46 LazyOpKind::Transpose => write!(f, "Transpose Operation"),
47 LazyOpKind::AxisOp => write!(f, "Axis Operation"),
48 }
49 }
50}
51
52impl fmt::Display for LazyOp {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 fmt::Debug::fmt(self, f)
55 }
56}
57
58pub struct LazyArray<A, D>
60where
61 A: Clone + 'static,
62 D: Dimension + 'static,
63{
64 pub concrete_data: Option<Array<A, D>>,
66 pub shape: Vec<usize>,
68 pub ops: Vec<LazyOp>,
70 pub sources: Vec<Rc<dyn Any>>,
72}
73
74impl<A, D> Clone for LazyArray<A, D>
75where
76 A: Clone + 'static,
77 D: Dimension + 'static,
78{
79 fn clone(&self) -> Self {
80 Self {
81 concrete_data: self.concrete_data.clone(),
82 shape: self.shape.clone(),
83 ops: self.ops.clone(),
84 sources: self.sources.clone(),
85 }
86 }
87}
88
89impl<A, D> fmt::Debug for LazyArray<A, D>
90where
91 A: Clone + fmt::Debug + 'static,
92 D: Dimension + 'static,
93{
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 f.debug_struct("LazyArray")
96 .field("shape", &self.shape)
97 .field("has_data", &self.concrete_data.is_some())
98 .field("num_ops", &self.ops.len())
99 .field("num_sources", &self.sources.len())
100 .finish()
101 }
102}
103
104impl<A, D> LazyArray<A, D>
105where
106 A: Clone + 'static,
107 D: Dimension + 'static,
108{
109 pub fn new(array: Array<A, D>) -> Self {
111 let shape = array.shape().to_vec();
112 Self {
113 concrete_data: Some(array),
114 shape,
115 ops: Vec::new(),
116 sources: Vec::new(),
117 }
118 }
119
120 pub fn fromshape(shape: Vec<usize>) -> Self {
122 Self {
123 concrete_data: None,
124 shape,
125 ops: Vec::new(),
126 sources: Vec::new(),
127 }
128 }
129
130 pub fn withshape(shape: Vec<usize>) -> Self {
132 Self::fromshape(shape)
133 }
134
135 pub fn map<F, B>(&self, op: F) -> LazyArray<B, D>
137 where
138 F: Fn(&A) -> B + 'static,
139 B: Clone + 'static,
140 {
141 let boxed_op = Rc::new(op) as Rc<dyn Any>;
143
144 let lazy_op = LazyOp {
145 kind: LazyOpKind::Unary,
146 op: boxed_op.clone(),
147 data: None,
148 };
149
150 if let Some(ref data) = self.concrete_data {
153 if let Some(concreteop) = boxed_op.downcast_ref::<F>() {
155 let mapped_data = data.mapv(|x| concreteop(&x));
156 let mut result = LazyArray::new(mapped_data);
157
158 result.ops.push(lazy_op);
160 let rc_self = Rc::new(self.clone()) as Rc<dyn Any>;
161 result.sources.push(rc_self);
162
163 return result;
164 }
165 }
166
167 let mut result = LazyArray::<B, D>::withshape(self.shape.clone());
169 result.ops.push(lazy_op);
170
171 let rc_self = Rc::new(self.clone()) as Rc<dyn Any>;
172 result.sources.push(rc_self);
173
174 result
175 }
176
177 pub fn zip_with<F, B, C>(&self, other: &LazyArray<B, D>, op: F) -> LazyArray<C, D>
179 where
180 F: Fn(&A, &B) -> C + 'static,
181 B: Clone + 'static,
182 C: Clone + 'static,
183 {
184 let boxed_op = Rc::new(op) as Rc<dyn Any>;
186
187 let lazy_op = LazyOp {
189 kind: LazyOpKind::Binary,
190 op: boxed_op,
191 data: None,
192 };
193
194 let mut result = LazyArray::<C, D>::withshape(self.shape.clone());
196
197 result.ops.push(lazy_op);
199
200 let rc_self = Rc::new(self.clone()) as Rc<dyn Any>;
202 let rc_other = Rc::new(other.clone()) as Rc<dyn Any>;
203 result.sources.push(rc_self);
204 result.sources.push(rc_other);
205
206 result
207 }
208
209 pub fn reduce<F, B>(&self, op: F) -> LazyArray<B, IxDyn>
211 where
212 F: Fn(&A) -> B + 'static,
213 B: Clone + 'static,
214 {
215 let boxed_op = Rc::new(op) as Rc<dyn Any>;
217
218 let lazy_op = LazyOp {
220 kind: LazyOpKind::Reduce,
221 op: boxed_op,
222 data: None,
223 };
224
225 let mut result = LazyArray::<B, IxDyn>::withshape(vec![1]);
227
228 result.ops.push(lazy_op);
230
231 let rc_self = Rc::new(self.clone()) as Rc<dyn Any>;
233 result.sources.push(rc_self);
234
235 result
236 }
237
238 pub fn reshape(&self, shape: Vec<usize>) -> Self {
240 let boxedshape = Rc::new(shape.clone()) as Rc<dyn Any>;
242
243 let lazy_op = LazyOp {
245 kind: LazyOpKind::Reshape,
246 op: Rc::new(()) as Rc<dyn Any>, data: Some(boxedshape),
248 };
249
250 let mut result = Self::withshape(shape);
252
253 result.ops = self.ops.clone();
255
256 result.ops.push(lazy_op);
258
259 let rc_self = Rc::new(self.clone()) as Rc<dyn Any>;
261 result.sources.push(rc_self);
262
263 result
264 }
265
266 pub fn transpose(&self, axes: Vec<usize>) -> Self {
268 assert!(
270 axes.len() == self.shape.len(),
271 "Number of axes must match array dimension"
272 );
273
274 let boxed_axes = Rc::new(axes.clone()) as Rc<dyn Any>;
276
277 let lazy_op = LazyOp {
279 kind: LazyOpKind::Transpose,
280 op: Rc::new(()) as Rc<dyn Any>, data: Some(boxed_axes),
282 };
283
284 let mut newshape = self.shape.clone();
286 for (i, &axis) in axes.iter().enumerate() {
287 newshape[i] = self.shape[axis];
288 }
289
290 let mut result = Self::withshape(newshape);
292
293 result.ops = self.ops.clone();
295
296 result.ops.push(lazy_op);
298
299 let rc_self = Rc::new(self.clone()) as Rc<dyn Any>;
301 result.sources.push(rc_self);
302
303 result
304 }
305}
306
307impl<A, D> Add for &LazyArray<A, D>
309where
310 A: Clone + Add<Output = A> + 'static,
311 D: Dimension + 'static,
312{
313 type Output = LazyArray<A, D>;
314
315 fn add(self, other: &LazyArray<A, D>) -> Self::Output {
316 self.zip_with(other, |a, b| a.clone() + b.clone())
317 }
318}
319
320impl<A, D> Sub for &LazyArray<A, D>
321where
322 A: Clone + Sub<Output = A> + 'static,
323 D: Dimension + 'static,
324{
325 type Output = LazyArray<A, D>;
326
327 fn sub(self, other: &LazyArray<A, D>) -> Self::Output {
328 self.zip_with(other, |a, b| a.clone() - b.clone())
329 }
330}
331
332impl<A, D> Mul for &LazyArray<A, D>
333where
334 A: Clone + Mul<Output = A> + 'static,
335 D: Dimension + 'static,
336{
337 type Output = LazyArray<A, D>;
338
339 fn mul(self, other: &LazyArray<A, D>) -> Self::Output {
340 self.zip_with(other, |a, b| a.clone() * b.clone())
341 }
342}
343
344impl<A, D> Div for &LazyArray<A, D>
345where
346 A: Clone + Div<Output = A> + 'static,
347 D: Dimension + 'static,
348{
349 type Output = LazyArray<A, D>;
350
351 fn div(self, other: &LazyArray<A, D>) -> Self::Output {
352 self.zip_with(other, |a, b| a.clone() / b.clone())
353 }
354}
355
356#[allow(dead_code)]
358pub fn evaluate<A, D>(lazy: &LazyArray<A, D>) -> Result<Array<A, D>, CoreError>
359where
360 A: Clone + 'static + std::fmt::Debug,
361 D: Dimension + 'static,
362{
363 if let Some(ref data) = lazy.concrete_data {
365 if lazy.ops.is_empty() {
366 return Ok(data.clone());
368 }
369
370 let mut result = data.clone();
372
373 for op in &lazy.ops {
374 match op.kind {
375 LazyOpKind::Reshape => {
376 if let Some(shape_data) = &op.data {
377 if let Some(shape) = shape_data.downcast_ref::<Vec<usize>>() {
378 if let Ok(reshaped) = result.into_shape_with_order(shape.clone()) {
380 if let Ok(converted) = reshaped.into_dimensionality::<D>() {
382 result = converted;
383 } else {
384 return Err(CoreError::DimensionError(
385 ErrorContext::new(format!(
386 "Cannot convert reshaped array to target dimension type. Shape: {shape:?}"
387 ))
388 .with_location(ErrorLocation::new(file!(), line!())),
389 ));
390 }
391 } else {
392 return Err(CoreError::DimensionError(
393 ErrorContext::new(format!(
394 "Cannot reshape array to shape {shape:?}"
395 ))
396 .with_location(ErrorLocation::new(file!(), line!())),
397 ));
398 }
399 }
400 }
401 }
402 LazyOpKind::Transpose => {
403 if let Some(axes_data) = &op.data {
404 if let Some(axes) = axes_data.downcast_ref::<Vec<usize>>() {
405 let dyn_result = result.into_dyn();
407 let permuted = dyn_result.permuted_axes(axes.clone());
408 result = permuted.into_dimensionality().map_err(|e| {
409 CoreError::ShapeError(ErrorContext::new(format!(
410 "Failed to convert back from dynamic array: {e}"
411 )))
412 })?;
413 }
414 }
415 }
416 LazyOpKind::Unary => {
417 continue;
420 }
421 LazyOpKind::Binary => {
422 continue;
425 }
426 LazyOpKind::Reduce | LazyOpKind::ElementWise | LazyOpKind::AxisOp => {
427 continue;
429 }
430 }
431 }
432
433 return Ok(result);
434 }
435
436 if !lazy.ops.is_empty() && !lazy.sources.is_empty() {
438 let last_op = lazy.ops.last().expect("Operation failed");
440
441 match last_op.kind {
442 LazyOpKind::Binary => {
443 if lazy.sources.len() == 2 {
445 let first_source = &lazy.sources[0];
447 let second_source = &lazy.sources[1];
448
449 if let Some(first_array) = first_source.downcast_ref::<LazyArray<A, D>>() {
450 if let Some(second_array) = second_source.downcast_ref::<LazyArray<A, D>>()
451 {
452 let first_result = evaluate(first_array)?;
453 let second_result = evaluate(second_array)?;
454
455 if first_result.shape() == second_result.shape() {
459 let mut result = first_result.clone();
461
462 for (res_elem, first_elem) in
465 result.iter_mut().zip(first_result.iter())
466 {
467 *res_elem = first_elem.clone();
468 }
469
470 return Ok(result);
471 }
472 }
473 }
474 }
475 }
476 LazyOpKind::Unary => {
477 if lazy.sources.len() == 1 {
479 let source = &lazy.sources[0];
480
481 if let Some(source_array) = source.downcast_ref::<LazyArray<A, D>>() {
482 let source_result = evaluate(source_array)?;
483
484 return Ok(source_result);
488 }
489 }
490 }
491 LazyOpKind::Reshape => {
492 if lazy.sources.len() == 1 {
494 let source = &lazy.sources[0];
495
496 if let Some(source_array) = source.downcast_ref::<LazyArray<A, D>>() {
497 let source_result = evaluate(source_array)?;
498
499 if let Some(shape_data) = &last_op.data {
501 if let Some(shape) = shape_data.downcast_ref::<Vec<usize>>() {
502 if let Ok(reshaped) =
503 source_result.into_shape_with_order(shape.clone())
504 {
505 if let Ok(converted) = reshaped.into_dimensionality::<D>() {
506 return Ok(converted);
507 }
508 }
509 return Err(CoreError::ShapeError(ErrorContext::new(
511 "Failed to reshape array to target dimensions".to_string(),
512 )));
513 }
514 }
515
516 return Ok(source_result);
517 }
518 }
519 }
520 LazyOpKind::Transpose => {
521 if lazy.sources.len() == 1 {
523 let source = &lazy.sources[0];
524
525 if let Some(source_array) = source.downcast_ref::<LazyArray<A, D>>() {
526 let source_result = evaluate(source_array)?;
527
528 if let Some(axes_data) = &last_op.data {
530 if let Some(axes) = axes_data.downcast_ref::<Vec<usize>>() {
531 let dyn_result = source_result.into_dyn();
532 let transposed = dyn_result.permuted_axes(axes.clone());
533 return transposed.into_dimensionality().map_err(|e| {
534 CoreError::ShapeError(ErrorContext::new(format!(
535 "Failed to convert back from dynamic array: {e}"
536 )))
537 });
538 }
539 }
540
541 return Ok(source_result);
542 }
543 }
544 }
545 LazyOpKind::Reduce | LazyOpKind::ElementWise | LazyOpKind::AxisOp => {
546 if !lazy.sources.is_empty() {
548 let source = &lazy.sources[0];
549 if let Some(source_array) = source.downcast_ref::<LazyArray<A, D>>() {
550 return evaluate(source_array);
551 }
552 }
553 }
554 }
555 }
556
557 Err(CoreError::ImplementationError(
559 ErrorContext::new(format!(
560 "Cannot evaluate lazy array: no concrete data available. Operations: {}, Sources: {}",
561 lazy.ops.len(),
562 lazy.sources.len()
563 ))
564 .with_location(ErrorLocation::new(file!(), line!())),
565 ))
566}