pub struct Matrix<T> { /* private fields */ }Expand description
An owned matrix: a layout, the length of each dimension, and the elements in that order.
use structio::{Matrix, MatrixLayout};
let m = Matrix::new(MatrixLayout::RowMajor, vec![2, 2], vec![1.0f64, 2.0, 3.0, 4.0]).unwrap();
assert_eq!(m.extents(), &[2, 2]);
let bytes = structio::to_beve(&m);
assert_eq!(structio::from_beve::<Matrix<f64>>(&bytes).unwrap(), m);
assert_eq!(
structio::beve_to_json(&bytes).unwrap(),
r#"{"layout":"layout_right","extents":[2,2],"value":[1,2,3,4]}"#
);The elements are one flat run, since that is what the format stores and what
makes storing it cheap. Indexing into them is arithmetic this type
deliberately does not do: which of the two layouts is in force changes the
answer, and a program that computes with matrices already has a type that
knows. Use Matrix::into_parts to hand the pieces to it.
Implementations§
Source§impl<T> Matrix<T>
impl<T> Matrix<T>
Sourcepub fn new(
layout: MatrixLayout,
extents: Vec<usize>,
data: Vec<T>,
) -> Result<Self, ErrorCode>
pub fn new( layout: MatrixLayout, extents: Vec<usize>, data: Vec<T>, ) -> Result<Self, ErrorCode>
Build a matrix, checking that the extents describe the data.
The only failure is InvalidMatrixShape,
and it is checked here so that it can never be checked again: nothing
downstream of this, writing included, has to consider a matrix whose
halves disagree.
Examples found in repository?
95fn main() -> Result<(), Box<dyn std::error::Error>> {
96 let value = run();
97
98 // -- The same value, either way ------------------------------------------
99 let text = structio::to_string(&value);
100 let binary = structio::to_beve(&value);
101
102 assert_eq!(structio::from_str::<Run>(&text)?, value);
103 assert_eq!(structio::from_beve::<Run>(&binary)?, value);
104
105 println!("json {:>6} bytes", text.len());
106 println!(
107 "beve {:>6} bytes ({:.0}% of the text)",
108 binary.len(),
109 100.0 * binary.len() as f64 / text.len() as f64
110 );
111 // The thousand samples are 8 bytes each and nothing more; the thousand
112 // booleans are one bit each.
113 println!(
114 " {} of those bytes are the sample payload, {} the flags",
115 value.samples.len() * 8,
116 value.valid.len().div_ceil(8)
117 );
118
119 // -- Reading into a value you already have -------------------------------
120 // The destination keeps its buffers, so a loop over many documents of the
121 // same shape settles into doing no allocation at all.
122 let mut into = Run::default();
123 structio::read_beve_into(&mut into, &binary)?;
124 let at = into.samples.as_ptr();
125 structio::read_beve_into(&mut into, &binary)?;
126 assert_eq!(into.samples.as_ptr(), at, "the allocation was reused");
127
128 // -- Borrowing straight out of the buffer --------------------------------
129 let frame = Frame {
130 id: 9,
131 payload: &[0xDE, 0xAD, 0xBE, 0xEF],
132 note: "no copies here",
133 };
134 let bytes = structio::to_beve(&frame);
135 let back: Frame = structio::from_beve(&bytes)?;
136 assert_eq!(back, frame);
137 let inside = (back.note.as_ptr() as usize) - (bytes.as_ptr() as usize) < bytes.len();
138 println!("borrowed fields point into the input: {inside}");
139
140 // -- Arrays a reader can point at ----------------------------------------
141 // BEVE's aligned form pads each numeric payload onto its own element
142 // width, so a reader with an aligned buffer can borrow the block where it
143 // lies instead of copying it out. The same document otherwise: this one
144 // still reads back into the same value.
145 assert_eq!(
146 structio::from_beve::<Run>(&structio::to_beve_aligned(&value))?,
147 value
148 );
149 let padded = structio::to_beve_aligned(&value.samples);
150 let payload = padded.len() - value.samples.len() * 8;
151 assert_eq!(payload % 8, 0);
152 println!(
153 "the aligned form puts {} samples at byte {payload}",
154 value.samples.len()
155 );
156
157 // And a reader takes that offer up: `try_slice` hands the block back as a
158 // `&[f64]` pointing into the document itself. Whether it can is partly the
159 // allocator's decision, the document having to sit on an address an `f64`
160 // could live at, so the answer is an `Option` rather than a promise, and
161 // `Cow<[f64]>` is the field type that copies when it has to.
162 let mut reader = structio::beve::Reader::new(&padded);
163 match reader.try_slice::<f64>() {
164 Some(block) => println!("borrowed {} samples with no copy at all", block.len()),
165 None => println!("this buffer is not one a &[f64] can point into"),
166 }
167
168 // -- Writing to a sink ---------------------------------------------------
169 // Drained as it is produced, so peak memory is the buffer rather than the
170 // size of the output.
171 let mut file = Vec::new();
172 structio::to_beve_writer(&value, &mut file)?;
173 assert_eq!(file, binary);
174
175 // -- Length-prefixed frames ----------------------------------------------
176 let runs = [run(), run()];
177 let mut sink = Vec::new();
178 send_frames(&runs, &mut sink)?;
179
180 // The unbuffered form is the same stream, byte for byte, which is the
181 // whole claim `beve_size` makes.
182 let mut streamed = Vec::new();
183 stream_frames(&runs, &mut streamed)?;
184 assert_eq!(streamed, sink);
185
186 let mut rest = &sink[..];
187 for want in &runs {
188 let (len, tail) = rest.split_at(4);
189 let len = u32::from_le_bytes(len.try_into()?) as usize;
190 let (frame, tail) = tail.split_at(len);
191 assert_eq!(&structio::from_beve::<Run>(frame)?, want);
192 rest = tail;
193 }
194 assert!(rest.is_empty());
195 println!("read back {} length-prefixed frames", runs.len());
196
197 // A body behind a header, in the aligned form. The payload lands on its
198 // element width counted from the start of the frame, whatever the query in
199 // front of it is, and the stated length is the body's own.
200 for query in ["", "/sensor", "/a/rather/longer/route"] {
201 let frame = frame_aligned(query, &value.samples);
202 let base = 8 + query.len();
203 let stated = u64::from_le_bytes(frame[..8].try_into()?) as usize;
204 assert_eq!(frame.len() - base, stated);
205 assert_eq!((frame.len() - value.samples.len() * 8) % 8, 0);
206 assert_eq!(
207 structio::from_beve::<Vec<f64>>(&frame[base..])?,
208 value.samples
209 );
210 }
211 println!("aligned bodies stay aligned behind a header of any length");
212
213 // -- Looking at a document you have no type for --------------------------
214 // No schema involved: the binary states every value's kind and extent, so
215 // the walk that reads it drives the JSON writer directly. What comes out is
216 // the same bytes the typed path above produced.
217 assert_eq!(structio::beve_to_json(&binary)?, text);
218 let unknown = structio::beve_to_json(&structio::to_beve(&frame))?;
219 println!("a document with no declared type: {unknown}");
220
221 // -- A file too large to hold --------------------------------------------
222 // `from_beve` wants the whole document. `Documents` wants one value of it,
223 // so the cost of a million records is one record, not a million. A typed
224 // array streams too: the elements carry no headers, and the one the array
225 // implied is supplied to the reader with each span.
226 let archive = structio::to_beve(&vec![run(), run(), run()]);
227 let mut docs = structio::beve::Documents::array(&archive[..]).read_size(16);
228 // Into an existing value, so the loop stops allocating after the first
229 // record as well as holding only one at a time.
230 let mut record = Run::default();
231 let mut count = 0;
232 while let Some(result) = docs.next_value_into(&mut record) {
233 result?;
234 count += 1;
235 }
236 println!(
237 "read {count} records out of {} bytes without ever holding the file",
238 archive.len()
239 );
240
241 // -- What BEVE carries that JSON cannot ----------------------------------
242 let odd = vec![f64::NAN, f64::INFINITY, -0.0];
243 let back: Vec<f64> = structio::from_beve(&structio::to_beve(&odd))?;
244 assert!(back[0].is_nan() && back[1].is_infinite() && back[2].is_sign_negative());
245 println!("NaN, infinity, and negative zero survive the round trip");
246
247 // -- Complex numbers and matrices ----------------------------------------
248 // BEVE's two data-carrying extensions have types, and both work in JSON as
249 // well, so a struct holding one still declares its schema once.
250 let signal = vec![
251 structio::Complex::new(1.0f64, 2.0),
252 structio::Complex::new(3.0, -4.0),
253 ];
254 let bytes = structio::to_beve(&signal);
255 println!(
256 "{} complex samples in {} bytes: one header, one count, and the components",
257 signal.len(),
258 bytes.len()
259 );
260 assert_eq!(
261 structio::from_beve::<Vec<structio::Complex<f64>>>(&bytes)?,
262 signal
263 );
264
265 // A matrix stores its data as an ordinary value, so a matrix of complex
266 // numbers is the run above with a shape in front of it.
267 let grid = structio::Matrix::new(structio::MatrixLayout::RowMajor, vec![1, 2], signal.clone())?;
268 let bytes = structio::to_beve(&grid);
269 assert_eq!(
270 structio::from_beve::<structio::Matrix<structio::Complex<f64>>>(&bytes)?,
271 grid
272 );
273 println!("as a matrix: {}", structio::beve_to_json(&bytes)?);
274
275 // -- Fields you do not know about ----------------------------------------
276 // A key no field claims is refused by default, which is what catches a
277 // typo or the wrong document. `SkipUnknown` asks for the other behaviour:
278 // take the members you recognize and step over the rest, so a producer can
279 // add fields without breaking you.
280 #[derive(Default, Debug, PartialEq)]
281 struct JustTheLabel {
282 label: String,
283 }
284 structio::object!(JustTheLabel { label });
285
286 let partial = structio::from_beve_with::<structio::SkipUnknown, JustTheLabel>(&binary)?;
287 assert_eq!(partial.label, value.label);
288 println!("unknown members are stepped over: {:?}", partial.label);
289
290 Ok(())
291}pub fn layout(&self) -> MatrixLayout
Sourcepub fn set_layout(&mut self, layout: MatrixLayout)
pub fn set_layout(&mut self, layout: MatrixLayout)
Reinterpret the same elements in the other storage order.
Nothing moves: the layout says how the elements the matrix already holds are to be read, so changing it changes what they mean.
pub fn data(&self) -> &[T]
Sourcepub fn data_mut(&mut self) -> &mut [T]
pub fn data_mut(&mut self) -> &mut [T]
The elements, mutably.
A slice rather than the Vec, which is what keeps the shape true: the
values may change and their number may not.
pub fn len(&self) -> usize
pub fn is_empty(&self) -> bool
Sourcepub fn into_parts(self) -> (MatrixLayout, Vec<usize>, Vec<T>)
pub fn into_parts(self) -> (MatrixLayout, Vec<usize>, Vec<T>)
Take the three pieces apart, which is where a matrix stops being this crate’s problem and starts being your linear algebra library’s.
Trait Implementations§
impl<T: Eq> Eq for Matrix<T>
Source§impl<'de, T> Read<'de> for Matrix<T>
impl<'de, T> Read<'de> for Matrix<T>
impl<T: PartialEq> StructuralPartialEq for Matrix<T>
Source§impl<T: Write> Write for Matrix<T>
impl<T: Write> Write for Matrix<T>
fn write<O: Options>(&self, w: &mut Writer<'_, O>)
Source§fn is_null(&self) -> bool
fn is_null(&self) -> bool
Options::SKIP_NULL. Read more