legume_numeric/matrix/
tensor_io.rs1use crate::matrix::common_io::{read_lines_of_types, write_lines, Delimiter};
2use crate::matrix::parquet::*;
3use crate::matrix::traits::*;
4
5use candle_core::{Device, Tensor};
6
7impl IoOps for Tensor {
8 type Scalar = f32;
9 type Mat = Self;
10
11 fn read_data(
12 file_path: &str,
13 delim: impl Into<Delimiter>,
14 skip: Option<usize>,
15 row_name_index: Option<usize>,
16 column_indices: Option<&[usize]>,
17 column_names: Option<&[Box<str>]>,
18 ) -> anyhow::Result<MatWithNames<Self::Mat>> {
19 let (rows, cols, data) = Self::read_data_vec_with_indices_names(
20 file_path,
21 delim,
22 skip,
23 row_name_index,
24 column_indices,
25 column_names,
26 )?;
27
28 let nrows = rows.len();
29 let ncols = cols.len();
30 Ok(MatWithNames {
31 rows,
32 cols,
33 mat: Tensor::from_vec(data, (nrows, ncols), &Device::Cpu)?,
34 })
35 }
36
37 fn read_file_delim(
38 tsv_file: &str,
39 delim: impl Into<Delimiter>,
40 skip: Option<usize>,
41 ) -> anyhow::Result<Self::Mat> {
42 let hdr_line = match skip {
43 Some(skip) => skip as i64,
44 None => -1, };
46
47 let data = read_lines_of_types::<f32>(tsv_file, delim, hdr_line)?.lines;
48
49 if data.is_empty() {
50 return Err(anyhow::anyhow!("No data in file"));
51 }
52
53 let ncols = data[0].len();
54 let nrows = data.len();
55 let data = data.into_iter().flatten().collect::<Vec<_>>();
56
57 Ok(Tensor::from_vec(data, (nrows, ncols), &Device::Cpu)?)
58 }
59
60 fn write_file_delim(&self, file: &str, delim: &str) -> anyhow::Result<()> {
61 let dims = self.dims();
62
63 if dims.len() != 2 {
64 return Err(anyhow::anyhow!("Expected 2 dimensions, got {}", dims.len()));
65 }
66
67 let lines: Vec<Box<str>> = (0..dims[0])
68 .map(|i| {
69 let row = self.narrow(0, i, 1).expect("failed to narrow in");
70 let flatten_row = row.flatten_to(1).expect("flatten");
71 let row_vec = flatten_row.to_vec1::<f32>().expect("to_vec1");
72 row_vec
73 .iter()
74 .map(|&x| format!("{}", x))
75 .collect::<Vec<_>>()
76 .join(delim)
77 .into_boxed_str()
78 })
79 .collect();
80
81 write_lines(&lines, file)?;
82
83 Ok(())
84 }
85
86 fn to_parquet_with_names(
87 &self,
88 file_path: &str,
89 row_names: (Option<&[Box<str>]>, Option<&str>),
90 column_names: Option<&[Box<str>]>,
91 ) -> anyhow::Result<()> {
92 let dims = self.dims();
93
94 if dims.len() != 2 {
95 return Err(anyhow::anyhow!("expected 2 dimensions, got {}", dims.len()));
96 }
97
98 let (nrows, ncols) = (dims[0], dims[1]);
99
100 let (row_names_slice, row_column_name) = row_names;
101
102 let writer = ParquetWriter::new(
103 file_path,
104 (nrows, ncols),
105 (row_names_slice, column_names),
106 None,
107 row_column_name,
108 )?;
109 let row_names = writer.row_names_vec();
110
111 if row_names.len() != nrows {
112 return Err(anyhow::anyhow!("row names don't match"));
113 }
114
115 let mut writer = writer.get_writer()?;
116 let mut row_group_writer = writer.next_row_group()?;
117 parquet_add_bytearray(&mut row_group_writer, row_names)?;
118
119 let tensor = self.to_dtype(candle_core::DType::F32)?;
120
121 for j in 0..ncols {
122 let data_j = tensor.narrow(1, j, 1)?.flatten_all()?.to_vec1::<f32>()?;
123 parquet_add_numeric_column(&mut row_group_writer, &data_j)?;
124 }
125 row_group_writer.close()?;
126 writer.close()?;
127 Ok(())
128 }
129
130 fn from_parquet_with_indices_names(
131 file_path: &str,
132 row_name_index: Option<usize>,
133 column_indices: Option<&[usize]>,
134 column_names: Option<&[Box<str>]>,
135 ) -> anyhow::Result<MatWithNames<Self>> {
136 let parquet = ParquetReader::new(file_path, row_name_index, column_indices, column_names)?;
137
138 let nrows = parquet.row_names.len();
139 let ncols = parquet.column_names.len();
140
141 let data: Vec<f32> = parquet
142 .row_major_data
143 .into_iter()
144 .map(|x| x as f32)
145 .collect();
146
147 Ok(MatWithNames {
148 rows: parquet.row_names,
149 cols: parquet.column_names,
150 mat: Tensor::from_vec(data, (nrows, ncols), &Device::Cpu)?,
151 })
152 }
153}