easy_ml/differentiation/container_record/iterators.rs
1/*!
2 * Record container iterators, for manipulating iterators of Records and converting back to
3 * Record containers.
4 */
5
6use crate::differentiation::{Index, Primitive, Record, WengertList};
7use crate::differentiation::{RecordMatrix, RecordTensor};
8use crate::matrices::iterators::{ColumnMajorIterator, RowMajorIterator, WithIndex};
9use crate::matrices::views::{MatrixRef, MatrixView, NoInteriorMutability};
10use crate::matrices::{Column, Matrix, Row};
11use crate::numeric::Numeric;
12use crate::tensors::indexing::TensorIterator;
13use crate::tensors::views::{TensorRef, TensorView};
14use crate::tensors::{Dimension, InvalidShapeError, Tensor};
15
16use std::error::Error;
17use std::fmt;
18use std::fmt::Debug;
19use std::iter::{ExactSizeIterator, FusedIterator};
20
21/**
22 * A wrapper around another iterator of record data and a history for that iterator's data
23 * that iterates though each element in the iterator as a [Record] instead.
24 *
25 * The main purpose of this representation is to allow manipulating one or more
26 * [RecordContainer](crate::differentiation::RecordContainer)s as iterators of Records then
27 * collect the iterator back into a RecordContainer so containers of Records with a shared history
28 * don't have to store the history for each element but the richer Record API can be used for
29 * operations anyway.
30 *
31 * ```
32 * use easy_ml::differentiation::{WengertList, Record, RecordTensor};
33 * use easy_ml::tensors::Tensor;
34 * use easy_ml::numeric::extra::Real;
35 *
36 * let history = WengertList::new();
37 * let A = RecordTensor::variables(
38 * &history,
39 * Tensor::from_fn([("r", 3), ("c", 2)], |[r, c]| ((5 * r) + c) as f32)
40 * );
41 * let B = RecordTensor::variables(
42 * &history,
43 * Tensor::from([("x", 6)], vec![ 0.2, 0.1, 0.5, 0.3, 0.7, 0.9 ])
44 * );
45 *
46 * fn power<T: Real+ Copy>(x: T, y: T) -> T {
47 * x.pow(y)
48 * }
49 *
50 * let result: RecordTensor<_, _, 2> = RecordTensor::from_iter(
51 * A.shape(),
52 * // iterators of records don't need to have matching shapes as long as the number
53 * // of elements matches the final shape
54 * A.iter_as_records().zip(B.iter_as_records()).map(|(x, y)| power(x, y))
55 * ).expect("result should have 6 elements");
56 * ```
57 */
58pub struct AsRecords<'a, I, T> {
59 numbers: I,
60 history: Option<&'a WengertList<T>>,
61}
62
63/**
64 * AsRecords can be created from a RecordTensor to manipulate the data as an iterator of Records
65 * then streamed back into a RecordTensor with [from_iter](RecordTensor::from_iter)
66 *
67 * See also: [map](RecordTensor::map), [map_mut](RecordTensor::map_mut)
68 *
69 * ```
70 * use easy_ml::differentiation::{WengertList, Record, RecordTensor};
71 * use easy_ml::tensors::Tensor;
72 *
73 * let history = WengertList::new();
74 * let X = RecordTensor::constants(
75 * Tensor::from_fn([("r", 2), ("c", 2)], |[r, c]| (r + c) as f32)
76 * );
77 * let y = Record::variable(1.0, &history);
78 * let result = RecordTensor::from_iter(
79 * [("r", 2), ("c", 2)],
80 * // Here we create each variable z from the constant in X and the variable y.
81 * // If we just did X + 1.0 we'd still have only constants, and we can't do X + y
82 * // directly because those traits aren't implemented.
83 * X.iter_as_records().map(|x| x + y)
84 * );
85 * // we can unwrap here because we know the iterator still contains 4 elements and they all
86 * // have the same WengertList so we can convert back to a RecordTensor (which is now
87 * // variables instead of constants)
88 * let Z = result.unwrap();
89 * let Z_indexing = Z.index();
90 * assert_eq!(1.0, Z_indexing.get([0, 0]).0);
91 * assert_eq!(2.0, Z_indexing.get([0, 1]).0);
92 * assert_eq!(3.0, Z_indexing.get([1, 1]).0);
93 * ```
94 */
95impl<'a, 'b, T, S, const D: usize>
96 AsRecords<'a, TensorIterator<'b, (T, Index), RecordTensor<'a, T, S, D>, D>, T>
97where
98 T: Numeric + Primitive,
99 S: TensorRef<(T, Index), D>,
100{
101 /**
102 * Given a record tensor returns an iterator of Records
103 *
104 * ```
105 * use easy_ml::differentiation::{WengertList, Record, RecordTensor};
106 * use easy_ml::differentiation::iterators::AsRecords;
107 * use easy_ml::tensors::Tensor;
108 *
109 * let history = WengertList::new();
110 * let X = RecordTensor::variables(
111 * &history,
112 * Tensor::from_fn([("r", 2), ("c", 2)], |[r, c]| (r + c) as f32)
113 * );
114 * let iter = X.iter_as_records(); // shorthand helper method
115 * let also_iter = AsRecords::from_tensor(&X);
116 * ```
117 */
118 pub fn from_tensor(tensor: &'b RecordTensor<'a, T, S, D>) -> Self {
119 AsRecords::from(tensor.history, TensorIterator::from(tensor))
120 }
121}
122
123/**
124 * AsRecords can be created from a RecordMatrix to manipulate the data as an iterator of Records
125 * then streamed back into a RecordMatrix with [from_iter](RecordMatrix::from_iter)
126 *
127 * See also: [map](RecordMatrix::map), [map_mut](RecordMatrix::map_mut)
128 *
129 * ```
130 * use easy_ml::differentiation::{WengertList, Record, RecordMatrix};
131 * use easy_ml::matrices::Matrix;
132 *
133 * let history = WengertList::new();
134 * let X = RecordMatrix::constants(
135 * Matrix::from_fn((2, 2), |(r, c)| (r + c) as f32)
136 * );
137 * let y = Record::variable(1.0, &history);
138 * let result = RecordMatrix::from_iter(
139 * (2, 2),
140 * // Here we create each variable z from the constant in X and the variable y.
141 * // If we just did X + 1.0 we'd still have only constants, and we can't do X + y
142 * // directly because those traits aren't implemented.
143 * X.iter_row_major_as_records().map(|x| x + y)
144 * );
145 * // we can unwrap here because we know the iterator still contains 4 elements and they all
146 * // have the same WengertList so we can convert back to a RecordMatrix (which is now
147 * // variables instead of constants)
148 * let Z = result.unwrap();
149 * let Z_view = Z.view();
150 * assert_eq!(1.0, Z_view.get(0, 0).0);
151 * assert_eq!(2.0, Z_view.get(0, 1).0);
152 * assert_eq!(3.0, Z_view.get(1, 1).0);
153 * ```
154 */
155impl<'a, 'b, T, S> AsRecords<'a, RowMajorIterator<'b, (T, Index), RecordMatrix<'a, T, S>>, T>
156where
157 T: Numeric + Primitive,
158 S: MatrixRef<(T, Index)> + NoInteriorMutability,
159{
160 /**
161 * Given a record matrix returns a row major iterator of Records
162 *
163 * ```
164 * use easy_ml::differentiation::{WengertList, Record, RecordMatrix};
165 * use easy_ml::differentiation::iterators::AsRecords;
166 * use easy_ml::matrices::Matrix;
167 *
168 * let history = WengertList::new();
169 * let X = RecordMatrix::variables(
170 * &history,
171 * Matrix::from_fn((2, 2), |(r, c)| (r + c) as f32)
172 * );
173 * let iter = X.iter_row_major_as_records(); // shorthand helper method
174 * let also_iter = AsRecords::from_matrix_row_major(&X);
175 * ```
176 */
177 pub fn from_matrix_row_major(matrix: &'b RecordMatrix<'a, T, S>) -> Self {
178 AsRecords::from(matrix.history, RowMajorIterator::from(matrix))
179 }
180}
181
182impl<'a, 'b, T, S> AsRecords<'a, ColumnMajorIterator<'b, (T, Index), RecordMatrix<'a, T, S>>, T>
183where
184 T: Numeric + Primitive,
185 S: MatrixRef<(T, Index)> + NoInteriorMutability,
186{
187 /**
188 * Given a record matrix returns a column major iterator of Records
189 */
190 pub fn from_matrix_column_major(matrix: &'b RecordMatrix<'a, T, S>) -> Self {
191 AsRecords::from(matrix.history, ColumnMajorIterator::from(matrix))
192 }
193}
194
195impl<'a, I, T> AsRecords<'a, I, T>
196where
197 T: Numeric + Primitive,
198 I: Iterator<Item = (T, Index)>,
199{
200 /**
201 * Given the WengertList an iterator of record numbers are for, returns an iterator of Records
202 *
203 * **The inputs are not checked for validity**. It is possible to pass in the wrong Wengert
204 * list here or even numbers with indexes that aren't tracked on the WengertList.
205 *
206 * Where possible, consider using [from_tensor](AsRecords::from_tensor),
207 * [from_matrix_row_major](AsRecords::from_matrix_row_major) or
208 * [from_matrix_column_major](AsRecords::from_matrix_row_major) instead.
209 */
210 pub fn from(history: Option<&'a WengertList<T>>, numbers: I) -> Self {
211 AsRecords { numbers, history }
212 }
213}
214
215impl<'a, I, T> AsRecords<'a, I, T>
216where
217 T: Numeric + Primitive,
218 I: Iterator<Item = (T, Index)> + Into<WithIndex<I>>,
219{
220 /**
221 * An iterator of Records that is created from an iterator which can provide the index for
222 * each element can also be coverted to a [WithIndex](WithIndex) iterator and provide the
223 * index for each record.
224 *
225 * WithIndex appears twice in the return type because the original iterator itself is wrapped
226 * in WithIndex to create an iterator that provides indexes, and AsRecords must also be
227 * wrapped in WithIndex to implement the iterator trait with indexes from the original
228 * iterator's implementation.
229 *
230 * ```
231 * use easy_ml::differentiation::{WengertList, Record, RecordTensor};
232 * use easy_ml::tensors::Tensor;
233 *
234 * let history = WengertList::new();
235 * let X = RecordTensor::variables(
236 * &history,
237 * Tensor::from([("r", 2), ("c", 2)], vec![ 0.5, 1.5, 2.5, 3.5 ])
238 * );
239 * let Y = RecordTensor::from_iter(
240 * [("r", 2), ("c", 2)],
241 * // Most Easy ML matrix and tensor iterators implement Into<WithIndex<Self>>, so we can
242 * // call with_index after creating the iterator
243 * X.iter_as_records().with_index().map(|([r, c], x)| x + ((r + (2 * c)) as f32))
244 * ).unwrap(); // we can unwrap here because we know the iterator is still 4 elements
245 * // so matches the shape and we added constants to each Record element so the history
246 * // is still consistent
247 * assert_eq!(
248 * Tensor::from([("r", 2), ("c", 2)], vec![ 0.5, 3.5, 3.5, 6.5 ]),
249 * Y.view().map(|(x, _)| x)
250 * );
251 * ```
252 */
253 pub fn with_index(self) -> WithIndex<AsRecords<'a, WithIndex<I>, T>> {
254 WithIndex {
255 iterator: AsRecords {
256 numbers: self.numbers.into(),
257 history: self.history,
258 },
259 }
260 }
261}
262
263impl<'a, I, O, T> AsRecords<'a, I, T>
264where
265 T: Numeric + Primitive,
266 I: Iterator<Item = (O, (T, Index))>,
267{
268 /**
269 * Given the WengertList an iterator of indexes and record numbers are for, returns an
270 * iterator of indexes and Records
271 *
272 * **The inputs are not checked for validity**. It is possible to pass in the wrong Wengert
273 * list here or even numbers with indexes that aren't tracked on the WengertList.
274 *
275 * Where possible, consider using [with_index](AsRecords::with_index) on an existing iterator
276 * instead.
277 */
278 pub fn from_with_index(history: Option<&'a WengertList<T>>, numbers: I) -> Self {
279 AsRecords { numbers, history }
280 }
281}
282
283impl<'a, I, T> From<AsRecords<'a, I, T>> for WithIndex<AsRecords<'a, WithIndex<I>, T>>
284where
285 T: Numeric + Primitive,
286 I: Iterator<Item = (T, Index)> + Into<WithIndex<I>>,
287{
288 fn from(iterator: AsRecords<'a, I, T>) -> Self {
289 iterator.with_index()
290 }
291}
292
293/**
294 * AsRecords is an iterator of [Record](Record)s, merging the history together with each iterator
295 * element.
296 */
297impl<'a, I, T> Iterator for AsRecords<'a, I, T>
298where
299 T: Numeric + Primitive,
300 I: Iterator<Item = (T, Index)>,
301{
302 type Item = Record<'a, T>;
303
304 fn next(&mut self) -> Option<Self::Item> {
305 self.numbers
306 .next()
307 .map(|number| Record::from_existing(number, self.history))
308 }
309
310 fn size_hint(&self) -> (usize, Option<usize>) {
311 self.numbers.size_hint()
312 }
313}
314
315impl<'a, I, T> FusedIterator for AsRecords<'a, I, T>
316where
317 T: Numeric + Primitive,
318 I: Iterator<Item = (T, Index)> + FusedIterator,
319{
320}
321
322impl<'a, I, T> ExactSizeIterator for AsRecords<'a, I, T>
323where
324 T: Numeric + Primitive,
325 I: Iterator<Item = (T, Index)> + ExactSizeIterator,
326{
327}
328
329/**
330 * When AsRecords contains an iterator `I` with the index `O` for each element, it is an iterator
331 * of `O` and [Record]s, merging the history together with each iterator element.
332 *
333 * Depending on what iterator and `with_index` implementation was used, `O` might be the tuple
334 * indexes for a matrix or the `const D: usize` length array of indexes for a tensor. In either
335 * case the iterator implementation for `WithIndex<AsRecords<..>>` just forwards the `O` values
336 * unchanged so it works with both.
337 */
338impl<'a, I, O, T> Iterator for WithIndex<AsRecords<'a, I, T>>
339where
340 T: Numeric + Primitive,
341 I: Iterator<Item = (O, (T, Index))>,
342{
343 type Item = (O, Record<'a, T>);
344
345 fn next(&mut self) -> Option<Self::Item> {
346 self.iterator
347 .numbers
348 .next()
349 .map(|(i, number)| (i, Record::from_existing(number, self.iterator.history)))
350 }
351
352 fn size_hint(&self) -> (usize, Option<usize>) {
353 self.iterator.numbers.size_hint()
354 }
355}
356
357impl<'a, I, O, T> FusedIterator for WithIndex<AsRecords<'a, I, T>>
358where
359 T: Numeric + Primitive,
360 I: Iterator<Item = (O, (T, Index))> + FusedIterator,
361{
362}
363
364impl<'a, I, O, T> ExactSizeIterator for WithIndex<AsRecords<'a, I, T>>
365where
366 T: Numeric + Primitive,
367 I: Iterator<Item = (O, (T, Index))> + ExactSizeIterator,
368{
369}
370
371/**
372 * An error due to an invalid record iterator. One of three cases
373 *
374 * - `Shape`: the iterator data didn't match the number of elements needed for a given shape to
375 * convert back into a record container
376 * - `Empty`: the iterator was empty, which is always an invalid length for any shape
377 * - `InconsistentHistory`: the iterator contains inconsistent histories in its data and so cannot
378 * be converted into a record container because a record container can only have one history for
379 * all its data
380 */
381#[derive(Clone, Debug)]
382pub enum InvalidRecordIteratorError<'a, T, const D: usize> {
383 Shape {
384 requested: InvalidShapeError<D>,
385 length: usize,
386 },
387 Empty,
388 InconsistentHistory(InconsistentHistory<'a, T>),
389}
390
391/**
392 * An error due to trying to create a RecordContainer with record data that has more than one
393 * history. Since RecordContainer stores the history once for all records it contains, it cannot
394 * support constants + variables or variables from multiple WengertLists.
395 */
396#[derive(Clone, Debug)]
397pub struct InconsistentHistory<'a, T> {
398 pub first: Option<&'a WengertList<T>>,
399 pub later: Option<&'a WengertList<T>>,
400}
401
402impl<'a, T, const D: usize> fmt::Display for InvalidRecordIteratorError<'a, T, D>
403where
404 T: Debug,
405{
406 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
407 match self {
408 Self::Shape { requested, length } => write!(
409 f,
410 "Shape {:?} does not match size of data {}",
411 requested.shape(),
412 length
413 ),
414 Self::Empty => write!(
415 f,
416 "Iterator was empty but all tensors and matrices must contain at least one element"
417 ),
418 Self::InconsistentHistory(h) => write!(
419 f,
420 "First history in iterator of records was {:?} but a later history in iterator was {:?}, record container cannot support different histories for a single tensor or matrix.",
421 h.first, h.later,
422 ),
423 }
424 }
425}
426
427impl<'a, T> fmt::Display for InconsistentHistory<'a, T>
428where
429 T: Debug,
430{
431 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
432 write!(
433 f,
434 "First history was {:?} but a later history in iterator was {:?}, record container cannot support different histories for a single tensor or matrix.",
435 self.first, self.later,
436 )
437 }
438}
439
440impl<'a, T, const D: usize> Error for InvalidRecordIteratorError<'a, T, D> where T: Debug {}
441
442impl<'a, T> Error for InconsistentHistory<'a, T> where T: Debug {}
443
444struct RecordContainerComponents<'a, T> {
445 history: Option<&'a WengertList<T>>,
446 numbers: Vec<(T, Index)>,
447}
448
449/// Converts an iterator of Records into the shared, consistent, history and a vec of (T, Index)
450/// or fails if the history is not consistent or the iterator is empty.
451fn collect_into_components<'a, T, I, const D: usize>(
452 iter: I,
453) -> Result<RecordContainerComponents<'a, T>, InvalidRecordIteratorError<'a, T, D>>
454where
455 T: Numeric + Primitive,
456 I: IntoIterator<Item = Record<'a, T>>,
457{
458 use crate::differentiation::record_operations::are_exact_same_list;
459
460 let mut history: Option<Option<&WengertList<T>>> = None;
461 let mut error: Option<InvalidRecordIteratorError<'a, T, D>> = None;
462
463 let numbers: Vec<(T, Index)> = iter
464 .into_iter()
465 .map(|record| {
466 match history {
467 None => history = Some(record.history),
468 Some(h) => {
469 if !are_exact_same_list(h, record.history) {
470 error = Some(InvalidRecordIteratorError::InconsistentHistory(
471 InconsistentHistory {
472 first: h,
473 later: record.history,
474 },
475 ));
476 }
477 }
478 }
479 (record.number, record.index)
480 })
481 .collect();
482
483 if let Some(error) = error {
484 return Err(error);
485 }
486
487 let data_length = numbers.len();
488 if data_length == 0 {
489 Err(InvalidRecordIteratorError::Empty)
490 } else {
491 // We already checked if the iterator was empty so `history` is always `Some` here
492 Ok(RecordContainerComponents {
493 history: history.unwrap(),
494 numbers,
495 })
496 }
497}
498
499/// Converts an iterator of an array of Records into n shared, consistent, histories and vecs of
500/// (T, Index) or fails individually if a history is not consistent or for all N if the iterator
501/// is empty.
502fn collect_into_n_components<'a, T, I, const D: usize, const N: usize>(
503 iter: I,
504) -> [Result<RecordContainerComponents<'a, T>, InvalidRecordIteratorError<'a, T, D>>; N]
505where
506 T: Numeric + Primitive,
507 I: IntoIterator<Item = [Record<'a, T>; N]>,
508{
509 use crate::differentiation::record_operations::are_exact_same_list;
510
511 let iter = iter.into_iter();
512
513 // We have N unique histories, potential errors, and vecs of numbers
514
515 let mut histories: [Option<Option<&WengertList<T>>>; N] = [None; N];
516
517 let mut errors: [Option<InvalidRecordIteratorError<'a, T, D>>; N] =
518 std::array::from_fn(|_| None);
519
520 let mut numbers: [Vec<(T, usize)>; N] =
521 std::array::from_fn(|_| Vec::with_capacity(iter.size_hint().0));
522
523 // The entire benefit to this method is still streaming all the record iterators without ever
524 // collecting more than one element's worth of records at a time, so we build up each vec
525 // of numbers together as we pass through the iterator and discard the duplicate histories
526 for records in iter {
527 for (n, record) in records.into_iter().enumerate() {
528 let history = &mut histories[n];
529 let error = &mut errors[n];
530 match history {
531 None => *history = Some(record.history),
532 Some(h) => {
533 if !are_exact_same_list(*h, record.history) {
534 *error = Some(InvalidRecordIteratorError::InconsistentHistory(
535 InconsistentHistory {
536 first: *h,
537 later: record.history,
538 },
539 ));
540 }
541 }
542 }
543 numbers[n].push((record.number, record.index));
544 }
545 }
546
547 let mut histories = histories.into_iter();
548 let mut errors = errors.into_iter();
549 let mut numbers = numbers.into_iter();
550 std::array::from_fn(|_| {
551 // unwrap always succeeds because we're consuming 3 iterators each of length N a total of N
552 // times
553 let history = histories.next().unwrap();
554 let error = errors.next().unwrap();
555 let numbers = numbers.next().unwrap();
556 let data_length = numbers.len();
557 match error {
558 Some(error) => Err(error),
559 None => {
560 if data_length == 0 {
561 Err(InvalidRecordIteratorError::Empty)
562 } else {
563 // We already checked if the iterator was empty so `history` is always
564 // `Some` here
565 Ok(RecordContainerComponents {
566 history: history.unwrap(),
567 numbers,
568 })
569 }
570 }
571 }
572 })
573}
574
575impl<'a, T, const D: usize> RecordTensor<'a, T, Tensor<(T, Index), D>, D>
576where
577 T: Numeric + Primitive,
578{
579 /**
580 * Given an iterator of records and a matching shape, collects them back into a
581 * [RecordTensor](RecordTensor).
582 *
583 * This should generally be preferred over converting the iterator to a [Vec] of Records, since
584 * a Vec of Records has to store the [WengertList](WengertList) reference for each individual
585 * record whereas a RecordTensor only stores it once.
586 *
587 * However, since a RecordTensor only stores the WengertList once, this conversion will fail
588 * if there are different histories in the iterator. It also fails if the iterator is empty
589 * or doesn't match the number of elements for the shape.
590 *
591 * See also: [elements](crate::tensors::dimensions::elements)
592 */
593 pub fn from_iter<I>(
594 shape: [(Dimension, usize); D],
595 iter: I,
596 ) -> Result<Self, InvalidRecordIteratorError<'a, T, D>>
597 where
598 I: IntoIterator<Item = Record<'a, T>>,
599 {
600 let RecordContainerComponents { history, numbers } = collect_into_components(iter)?;
601 let data_length = numbers.len();
602 match Tensor::try_from(shape, numbers) {
603 Ok(numbers) => Ok(RecordTensor::from_existing(
604 history,
605 TensorView::from(numbers),
606 )),
607 Err(invalid_shape) => Err(InvalidRecordIteratorError::Shape {
608 requested: invalid_shape,
609 length: data_length,
610 }),
611 }
612 }
613
614 /**
615 * Given an iterator of N record pairs and a matching shape, collects them back into N
616 * [RecordTensor](RecordTensor)s.
617 *
618 * This should generally be preferred over converting the iterator to N [Vec]s of Records,
619 * since a Vec of Records has to store the [WengertList](WengertList) reference for each
620 * individual record whereas a RecordTensor only stores it once.
621 *
622 * However, since a RecordTensor only stores the WengertList once, this conversion will fail
623 * if there are different histories in the iterator. It also fails if the iterator is empty
624 * or doesn't match the number of elements for the shape. Each failure due to different
625 * histories is seperate, if the ith elements in the records of the iterator have a
626 * consistent history but the jth elements do not then the ith result will be Ok but the
627 * jth will be Err.
628 *
629 * See also: [elements](crate::tensors::dimensions::elements)
630 */
631 pub fn from_iters<I, const N: usize>(
632 shape: [(Dimension, usize); D],
633 iter: I,
634 ) -> [Result<Self, InvalidRecordIteratorError<'a, T, D>>; N]
635 where
636 I: IntoIterator<Item = [Record<'a, T>; N]>,
637 {
638 let mut components = collect_into_n_components(iter).into_iter();
639 std::array::from_fn(|_| match components.next().unwrap() {
640 Err(error) => Err(error),
641 Ok(RecordContainerComponents { history, numbers }) => {
642 let data_length = numbers.len();
643 match Tensor::try_from(shape, numbers) {
644 Ok(numbers) => Ok(RecordTensor::from_existing(
645 history,
646 TensorView::from(numbers),
647 )),
648 Err(invalid_shape) => Err(InvalidRecordIteratorError::Shape {
649 requested: invalid_shape,
650 length: data_length,
651 }),
652 }
653 }
654 })
655 }
656}
657
658impl<'a, T> RecordMatrix<'a, T, Matrix<(T, Index)>>
659where
660 T: Numeric + Primitive,
661{
662 /**
663 * Given an iterator of records and a matching size, collects them back into a
664 * [RecordMatrix](RecordMatrix).
665 *
666 * This should generally be preferred over converting the iterator to a [Vec] of Records, since
667 * a Vec of Records has to store the [WengertList](WengertList) reference for each individual
668 * record whereas a RecordMatrix only stores it once.
669 *
670 * However, since a RecordMatrix only stores the WengertList once, this conversion will fail
671 * if there are different histories in the iterator. It also fails if the iterator is empty
672 * or doesn't match the R x C number of elements expected.
673 */
674 pub fn from_iter<I>(
675 size: (Row, Column),
676 iter: I,
677 ) -> Result<Self, InvalidRecordIteratorError<'a, T, 2>>
678 where
679 I: IntoIterator<Item = Record<'a, T>>,
680 {
681 let RecordContainerComponents { history, numbers } = collect_into_components(iter)?;
682 let data_length = numbers.len();
683 if data_length == size.0 * size.1 {
684 Ok(RecordMatrix::from_existing(
685 history,
686 MatrixView::from(Matrix::from_flat_row_major(size, numbers)),
687 ))
688 } else {
689 Err(InvalidRecordIteratorError::Shape {
690 requested: InvalidShapeError::new([("rows", size.0), ("columns", size.1)]),
691 length: data_length,
692 })
693 }
694 }
695
696 /**
697 * Given an iterator of N record pairs and a matching shape, collects them back into N
698 * [RecordMatrix](RecordMatrix)s.
699 *
700 * This should generally be preferred over converting the iterator to N [Vec]s of Records,
701 * since a Vec of Records has to store the [WengertList](WengertList) reference for each
702 * individual record whereas a RecordMatrix only stores it once.
703 *
704 * However, since a RecordMatrix only stores the WengertList once, this conversion will fail
705 * if there are different histories in the iterator. It also fails if the iterator is empty
706 * or doesn't match the R x C number of elements expected. Each failure due to different
707 * histories is seperate, if the ith elements in the records of the iterator have a
708 * consistent history but the jth elements do not then the ith result will be Ok but the
709 * jth will be Err.
710 *
711 * See also: [elements](crate::tensors::dimensions::elements)
712 */
713 pub fn from_iters<I, const N: usize>(
714 size: (Row, Column),
715 iter: I,
716 ) -> [Result<Self, InvalidRecordIteratorError<'a, T, 2>>; N]
717 where
718 I: IntoIterator<Item = [Record<'a, T>; N]>,
719 {
720 let mut components = collect_into_n_components(iter).into_iter();
721 std::array::from_fn(|_| match components.next().unwrap() {
722 Err(error) => Err(error),
723 Ok(RecordContainerComponents { history, numbers }) => {
724 let data_length = numbers.len();
725 if data_length == size.0 * size.1 {
726 Ok(RecordMatrix::from_existing(
727 history,
728 MatrixView::from(Matrix::from_flat_row_major(size, numbers)),
729 ))
730 } else {
731 Err(InvalidRecordIteratorError::Shape {
732 requested: InvalidShapeError::new([("rows", size.0), ("columns", size.1)]),
733 length: data_length,
734 })
735 }
736 }
737 })
738 }
739}