1use std::collections::BTreeMap;
2use std::fs::File;
3use std::io::{Read, Write};
4use std::path::Path;
5
6use num_complex::Complex64;
7use zip::write::SimpleFileOptions;
8use zip::{CompressionMethod, ZipArchive, ZipWriter};
9
10use crate::basis::{Basis, ErasedState};
11use crate::operator::{
12 LinearOperator, MatrixFormat, Operator, QuantumComponent, QuantumOperator, materialize_dense,
13};
14use crate::{QmbedError, Result};
15
16pub const ARCHIVE_VERSION: u8 = 1;
17pub const BASIS_ARCHIVE_VERSION: u8 = 1;
19
20fn archive_error(error: impl std::fmt::Display) -> QmbedError {
21 QmbedError::Archive(error.to_string())
22}
23
24fn npy_header(descriptor: &str, shape: &[usize]) -> Result<Vec<u8>> {
25 let shape = if shape.len() == 1 {
26 format!("({},)", shape[0])
27 } else {
28 format!(
29 "({})",
30 shape
31 .iter()
32 .map(usize::to_string)
33 .collect::<Vec<_>>()
34 .join(", ")
35 )
36 };
37 let dictionary =
38 format!("{{'descr': '{descriptor}', 'fortran_order': False, 'shape': {shape}, }}");
39 let padding = (16 - ((10 + dictionary.len() + 1) % 16)) % 16;
40 let header_length = dictionary
41 .len()
42 .checked_add(padding)
43 .and_then(|value| value.checked_add(1))
44 .ok_or_else(|| QmbedError::Archive("NPY header length overflow".into()))?;
45 let header_length = u16::try_from(header_length)
46 .map_err(|_| QmbedError::Archive("NPY v1 header is too long".into()))?;
47 let mut output = Vec::with_capacity(10 + usize::from(header_length));
48 output.extend_from_slice(b"\x93NUMPY");
49 output.extend_from_slice(&[1, 0]);
50 output.extend_from_slice(&header_length.to_le_bytes());
51 output.extend_from_slice(dictionary.as_bytes());
52 output.extend(std::iter::repeat_n(b' ', padding));
53 output.push(b'\n');
54 Ok(output)
55}
56
57fn write_u8_npy(writer: &mut impl Write, values: &[u8]) -> Result<()> {
58 writer
59 .write_all(&npy_header("|u1", &[values.len()])?)
60 .map_err(archive_error)?;
61 writer.write_all(values).map_err(archive_error)
62}
63
64fn write_u64_npy(writer: &mut impl Write, values: &[u64]) -> Result<()> {
65 writer
66 .write_all(&npy_header("<u8", &[values.len()])?)
67 .map_err(archive_error)?;
68 for value in values {
69 writer
70 .write_all(&value.to_le_bytes())
71 .map_err(archive_error)?;
72 }
73 Ok(())
74}
75
76fn write_complex_npy(
77 writer: &mut impl Write,
78 rows: usize,
79 columns: usize,
80 values: &[Complex64],
81) -> Result<()> {
82 if values.len() != rows.saturating_mul(columns) {
83 return Err(QmbedError::DimensionMismatch(
84 "archive matrix values do not match its shape".into(),
85 ));
86 }
87 writer
88 .write_all(&npy_header("<c16", &[rows, columns])?)
89 .map_err(archive_error)?;
90 for value in values {
91 writer
92 .write_all(&value.re.to_le_bytes())
93 .and_then(|()| writer.write_all(&value.im.to_le_bytes()))
94 .map_err(archive_error)?;
95 }
96 Ok(())
97}
98
99fn parse_npy_header(reader: &mut impl Read) -> Result<(String, Vec<usize>)> {
100 let mut magic = [0_u8; 6];
101 reader.read_exact(&mut magic).map_err(archive_error)?;
102 if &magic != b"\x93NUMPY" {
103 return Err(QmbedError::Archive("invalid NPY magic".into()));
104 }
105 let mut version = [0_u8; 2];
106 reader.read_exact(&mut version).map_err(archive_error)?;
107 let header_length = match version[0] {
108 1 => {
109 let mut bytes = [0_u8; 2];
110 reader.read_exact(&mut bytes).map_err(archive_error)?;
111 usize::from(u16::from_le_bytes(bytes))
112 }
113 2 | 3 => {
114 let mut bytes = [0_u8; 4];
115 reader.read_exact(&mut bytes).map_err(archive_error)?;
116 usize::try_from(u32::from_le_bytes(bytes))
117 .map_err(|_| QmbedError::Archive("NPY header is too large".into()))?
118 }
119 _ => return Err(QmbedError::Archive("unsupported NPY version".into())),
120 };
121 let mut header = vec![0_u8; header_length];
122 reader.read_exact(&mut header).map_err(archive_error)?;
123 let header = std::str::from_utf8(&header).map_err(archive_error)?.trim();
124 if header.contains("'fortran_order': True") || header.contains("\"fortran_order\": True") {
125 return Err(QmbedError::Archive(
126 "Fortran-ordered NPY matrices are not supported".into(),
127 ));
128 }
129 let descriptor_marker = if header.contains("'descr':") {
130 "'descr':"
131 } else {
132 "\"descr\":"
133 };
134 let descriptor_tail = header
135 .split_once(descriptor_marker)
136 .ok_or_else(|| QmbedError::Archive("NPY descriptor is missing".into()))?
137 .1
138 .trim_start();
139 let quote = descriptor_tail
140 .chars()
141 .next()
142 .filter(|character| *character == '\'' || *character == '"')
143 .ok_or_else(|| QmbedError::Archive("invalid NPY descriptor".into()))?;
144 let descriptor = descriptor_tail[1..]
145 .split_once(quote)
146 .ok_or_else(|| QmbedError::Archive("invalid NPY descriptor".into()))?
147 .0
148 .to_string();
149 let shape_marker = if header.contains("'shape':") {
150 "'shape':"
151 } else {
152 "\"shape\":"
153 };
154 let shape_tail = header
155 .split_once(shape_marker)
156 .ok_or_else(|| QmbedError::Archive("NPY shape is missing".into()))?
157 .1;
158 let shape_text = shape_tail
159 .split_once('(')
160 .and_then(|(_, tail)| tail.split_once(')'))
161 .map(|(shape, _)| shape)
162 .ok_or_else(|| QmbedError::Archive("invalid NPY shape".into()))?;
163 let shape = shape_text
164 .split(',')
165 .map(str::trim)
166 .filter(|value| !value.is_empty())
167 .map(|value| value.parse::<usize>().map_err(archive_error))
168 .collect::<Result<Vec<_>>>()?;
169 Ok((descriptor, shape))
170}
171
172fn read_version(reader: &mut impl Read) -> Result<u8> {
173 let (descriptor, shape) = parse_npy_header(reader)?;
174 if descriptor != "|u1" || shape != [1] {
175 return Err(QmbedError::Archive("invalid archive-version array".into()));
176 }
177 let mut version = [0_u8; 1];
178 reader.read_exact(&mut version).map_err(archive_error)?;
179 Ok(version[0])
180}
181
182fn read_u64_vector(reader: &mut impl Read) -> Result<Vec<u64>> {
183 let (descriptor, shape) = parse_npy_header(reader)?;
184 if !matches!(descriptor.as_str(), "<u8" | "=u8" | "|u8") || shape.len() != 1 {
185 return Err(QmbedError::Archive(
186 "sparse index arrays must be one-dimensional uint64 arrays".into(),
187 ));
188 }
189 let mut values = Vec::with_capacity(shape[0]);
190 for _ in 0..shape[0] {
191 let mut bytes = [0_u8; 8];
192 reader.read_exact(&mut bytes).map_err(archive_error)?;
193 values.push(u64::from_le_bytes(bytes));
194 }
195 Ok(values)
196}
197
198fn read_complex_matrix(reader: &mut impl Read) -> Result<(usize, usize, Vec<Complex64>)> {
199 let (descriptor, shape) = parse_npy_header(reader)?;
200 if !matches!(descriptor.as_str(), "<c16" | "=c16" | "|c16") || shape.len() != 2 {
201 return Err(QmbedError::Archive(
202 "matrix.npy must be a C-order complex128 matrix".into(),
203 ));
204 }
205 let count = shape[0]
206 .checked_mul(shape[1])
207 .ok_or_else(|| QmbedError::Archive("archive matrix size overflow".into()))?;
208 let mut values = Vec::with_capacity(count);
209 for _ in 0..count {
210 let mut real = [0_u8; 8];
211 let mut imaginary = [0_u8; 8];
212 reader.read_exact(&mut real).map_err(archive_error)?;
213 reader.read_exact(&mut imaginary).map_err(archive_error)?;
214 values.push(Complex64::new(
215 f64::from_le_bytes(real),
216 f64::from_le_bytes(imaginary),
217 ));
218 }
219 Ok((shape[0], shape[1], values))
220}
221
222fn validate_metadata(key: &str, value: &str) -> Result<()> {
223 if key.is_empty()
224 || key
225 .chars()
226 .any(|character| !(character.is_ascii_alphanumeric() || "_.-".contains(character)))
227 {
228 return Err(QmbedError::Archive(
229 "archive metadata keys may contain only ASCII letters, digits, `_`, `-`, and `.`"
230 .into(),
231 ));
232 }
233 if value
234 .chars()
235 .any(|character| matches!(character, '\t' | '\r' | '\n'))
236 {
237 return Err(QmbedError::Archive(
238 "archive metadata values may not contain tabs or newlines".into(),
239 ));
240 }
241 Ok(())
242}
243
244#[derive(Clone, Debug)]
253pub struct BasisArchive {
254 width_bits: usize,
255 states: Vec<ErasedState>,
256 metadata: BTreeMap<String, String>,
257}
258
259impl BasisArchive {
260 pub fn new(width_bits: usize, states: impl IntoIterator<Item = ErasedState>) -> Result<Self> {
262 let states = states.into_iter().collect::<Vec<_>>();
263 if width_bits == 0 || states.iter().any(|state| state.width_bits() != width_bits) {
264 return Err(QmbedError::Archive(
265 "basis states must share one positive logical width".into(),
266 ));
267 }
268 let mut unique = std::collections::HashSet::with_capacity(states.len());
269 if states.iter().any(|state| !unique.insert(*state)) {
270 return Err(QmbedError::Archive(
271 "basis archives require unique physical states".into(),
272 ));
273 }
274 Ok(Self {
275 width_bits,
276 states,
277 metadata: BTreeMap::new(),
278 })
279 }
280
281 pub fn from_basis<B>(basis: &B, width_bits: usize) -> Result<Self>
283 where
284 B: Basis,
285 B::State: std::fmt::Display,
286 {
287 let states = (0..basis.len())
288 .map(|index| {
289 let state = basis.state(index)?;
290 ErasedState::from_decimal(width_bits, &state.to_string())
291 })
292 .collect::<Result<Vec<_>>>()?;
293 Self::new(width_bits, states)
294 }
295
296 pub const fn width_bits(&self) -> usize {
298 self.width_bits
299 }
300
301 pub fn states(&self) -> &[ErasedState] {
303 &self.states
304 }
305
306 pub fn insert_metadata(
308 &mut self,
309 key: impl Into<String>,
310 value: impl Into<String>,
311 ) -> Result<()> {
312 let key = key.into();
313 let value = value.into();
314 validate_metadata(&key, &value)?;
315 self.metadata.insert(key, value);
316 Ok(())
317 }
318
319 pub fn metadata(&self) -> impl Iterator<Item = (&str, &str)> {
321 self.metadata
322 .iter()
323 .map(|(key, value)| (key.as_str(), value.as_str()))
324 }
325
326 pub fn metadata_value(&self, key: &str) -> Option<&str> {
328 self.metadata.get(key).map(String::as_str)
329 }
330}
331
332pub fn save_basis_zip(path: impl AsRef<Path>, basis: &BasisArchive) -> Result<()> {
334 let file = File::create(path).map_err(archive_error)?;
335 let mut archive = ZipWriter::new(file);
336 let options = SimpleFileOptions::default().compression_method(CompressionMethod::Deflated);
337 archive
338 .start_file("basis_archive_version.npy", options)
339 .map_err(archive_error)?;
340 write_u8_npy(&mut archive, &[BASIS_ARCHIVE_VERSION])?;
341 archive
342 .start_file("basis_manifest.tsv", options)
343 .map_err(archive_error)?;
344 archive
345 .write_all(format!("width_bits\t{}\n", basis.width_bits).as_bytes())
346 .map_err(archive_error)?;
347 for state in &basis.states {
348 archive
349 .write_all(format!("state\t{}\n", state.to_decimal()).as_bytes())
350 .map_err(archive_error)?;
351 }
352 archive
353 .start_file("metadata.tsv", options)
354 .map_err(archive_error)?;
355 for (key, value) in &basis.metadata {
356 archive
357 .write_all(format!("{key}\t{value}\n").as_bytes())
358 .map_err(archive_error)?;
359 }
360 archive.finish().map_err(archive_error)?;
361 Ok(())
362}
363
364pub fn load_basis_zip(path: impl AsRef<Path>) -> Result<BasisArchive> {
366 let file = File::open(path).map_err(archive_error)?;
367 let mut archive = ZipArchive::new(file).map_err(archive_error)?;
368 {
369 let mut version = archive
370 .by_name("basis_archive_version.npy")
371 .map_err(archive_error)?;
372 let version = read_version(&mut version)?;
373 if version != BASIS_ARCHIVE_VERSION {
374 return Err(QmbedError::Archive(format!(
375 "unsupported basis archive version {version}"
376 )));
377 }
378 }
379 let manifest = {
380 let mut entry = archive
381 .by_name("basis_manifest.tsv")
382 .map_err(archive_error)?;
383 let mut text = String::new();
384 entry.read_to_string(&mut text).map_err(archive_error)?;
385 text
386 };
387 let mut lines = manifest.lines();
388 let width_bits = lines
389 .next()
390 .and_then(|line| line.strip_prefix("width_bits\t"))
391 .ok_or_else(|| QmbedError::Archive("basis width is missing".into()))?
392 .parse::<usize>()
393 .map_err(archive_error)?;
394 let states = lines
395 .map(|line| {
396 let value = line
397 .strip_prefix("state\t")
398 .ok_or_else(|| QmbedError::Archive("invalid basis state row".into()))?;
399 ErasedState::from_decimal(width_bits, value)
400 })
401 .collect::<Result<Vec<_>>>()?;
402 let mut basis = BasisArchive::new(width_bits, states)?;
403 let has_metadata = archive.file_names().any(|name| name == "metadata.tsv");
404 if has_metadata {
405 let mut entry = archive.by_name("metadata.tsv").map_err(archive_error)?;
406 let mut text = String::new();
407 entry.read_to_string(&mut text).map_err(archive_error)?;
408 for line in text.lines() {
409 let (key, value) = line
410 .split_once('\t')
411 .ok_or_else(|| QmbedError::Archive("invalid archive metadata row".into()))?;
412 basis.insert_metadata(key, value)?;
413 }
414 }
415 Ok(basis)
416}
417
418pub fn save_operator_npz(
420 operator: &(impl LinearOperator + ?Sized),
421 path: impl AsRef<Path>,
422) -> Result<()> {
423 let shape = operator.shape();
424 let values = materialize_dense(operator)?;
425 let file = File::create(path).map_err(archive_error)?;
426 let mut archive = ZipWriter::new(file);
427 let options = SimpleFileOptions::default().compression_method(CompressionMethod::Deflated);
428 archive
429 .start_file("archive_version.npy", options)
430 .map_err(archive_error)?;
431 write_u8_npy(&mut archive, &[ARCHIVE_VERSION])?;
432 archive
433 .start_file("matrix.npy", options)
434 .map_err(archive_error)?;
435 write_complex_npy(&mut archive, shape.0, shape.1, &values)?;
436 archive.finish().map_err(archive_error)?;
437 Ok(())
438}
439
440pub fn load_operator_npz(path: impl AsRef<Path>, format: MatrixFormat) -> Result<Operator> {
442 let file = File::open(path).map_err(archive_error)?;
443 let mut archive = ZipArchive::new(file).map_err(archive_error)?;
444 if let Ok(mut version_entry) = archive.by_name("archive_version.npy") {
445 let version = read_version(&mut version_entry)?;
446 if version != ARCHIVE_VERSION {
447 return Err(QmbedError::Archive(format!(
448 "unsupported operator archive version {version}"
449 )));
450 }
451 }
452 let mut matrix = archive.by_name("matrix.npy").map_err(archive_error)?;
453 let (rows, columns, values) = read_complex_matrix(&mut matrix)?;
454 Operator::from_dense(rows, columns, values)?.converted(format)
455}
456
457#[derive(Clone, Debug)]
458pub struct OperatorArchiveEntry {
459 pub operator: Operator,
460 pub default: Option<Complex64>,
461}
462
463#[derive(Clone, Debug, Default)]
464pub struct OperatorArchive {
465 entries: BTreeMap<String, OperatorArchiveEntry>,
466 metadata: BTreeMap<String, String>,
467}
468
469impl OperatorArchive {
470 pub fn new() -> Self {
471 Self::default()
472 }
473
474 pub fn insert(
475 &mut self,
476 name: impl Into<String>,
477 operator: Operator,
478 default: Option<Complex64>,
479 ) -> Result<()> {
480 let name = name.into();
481 if name.is_empty()
482 || name
483 .chars()
484 .any(|character| !(character.is_ascii_alphanumeric() || "_.-".contains(character)))
485 {
486 return Err(QmbedError::Archive(
487 "archive entry names may contain only ASCII letters, digits, `_`, `-`, and `.`"
488 .into(),
489 ));
490 }
491 if self
492 .entries
493 .insert(name.clone(), OperatorArchiveEntry { operator, default })
494 .is_some()
495 {
496 return Err(QmbedError::Archive(format!(
497 "duplicate archive entry {name:?}"
498 )));
499 }
500 Ok(())
501 }
502
503 pub fn get(&self, name: &str) -> Option<&OperatorArchiveEntry> {
504 self.entries.get(name)
505 }
506
507 pub fn iter(&self) -> impl Iterator<Item = (&str, &OperatorArchiveEntry)> {
508 self.entries
509 .iter()
510 .map(|(name, entry)| (name.as_str(), entry))
511 }
512
513 pub fn insert_metadata(
520 &mut self,
521 key: impl Into<String>,
522 value: impl Into<String>,
523 ) -> Result<()> {
524 let key = key.into();
525 let value = value.into();
526 validate_metadata(&key, &value)?;
527 self.metadata.insert(key, value);
528 Ok(())
529 }
530
531 pub fn metadata(&self) -> impl Iterator<Item = (&str, &str)> {
532 self.metadata
533 .iter()
534 .map(|(key, value)| (key.as_str(), value.as_str()))
535 }
536
537 pub fn metadata_value(&self, key: &str) -> Option<&str> {
538 self.metadata.get(key).map(String::as_str)
539 }
540
541 pub fn from_quantum_operator(operator: &QuantumOperator) -> Result<Self> {
542 let mut archive = Self::new();
543 for component in operator.components() {
544 archive.insert(
545 component.name(),
546 component.operator().clone(),
547 component.default(),
548 )?;
549 }
550 Ok(archive)
551 }
552
553 pub fn into_quantum_operator(self) -> Result<QuantumOperator> {
554 QuantumOperator::new(self.entries.into_iter().map(|(name, entry)| {
555 if let Some(default) = entry.default {
556 QuantumComponent::with_default(name, entry.operator, default)
557 } else {
558 QuantumComponent::required(name, entry.operator)
559 }
560 }))
561 }
562}
563
564fn format_name(format: MatrixFormat) -> &'static str {
565 match format {
566 MatrixFormat::Dense => "dense",
567 MatrixFormat::Csc => "csc",
568 MatrixFormat::Csr => "csr",
569 MatrixFormat::Dia => "dia",
570 MatrixFormat::MatrixFree => "matrix_free",
571 }
572}
573
574fn parse_format(value: &str) -> Result<MatrixFormat> {
575 match value {
576 "dense" => Ok(MatrixFormat::Dense),
577 "csc" => Ok(MatrixFormat::Csc),
578 "csr" => Ok(MatrixFormat::Csr),
579 "dia" => Ok(MatrixFormat::Dia),
580 "matrix_free" => Ok(MatrixFormat::MatrixFree),
581 _ => Err(QmbedError::Archive(format!(
582 "unknown archived matrix format {value:?}"
583 ))),
584 }
585}
586
587pub fn save_zip(path: impl AsRef<Path>, entries: &OperatorArchive) -> Result<()> {
589 if entries.entries.is_empty() {
590 return Err(QmbedError::Archive(
591 "an operator archive must contain at least one entry".into(),
592 ));
593 }
594 let file = File::create(path).map_err(archive_error)?;
595 let mut archive = ZipWriter::new(file);
596 let options = SimpleFileOptions::default().compression_method(CompressionMethod::Deflated);
597 archive
598 .start_file("archive_version.npy", options)
599 .map_err(archive_error)?;
600 write_u8_npy(&mut archive, &[ARCHIVE_VERSION])?;
601
602 let mut manifest = String::new();
603 for (index, (name, entry)) in entries.entries.iter().enumerate() {
604 let shape = entry.operator.shape();
605 let (present, real, imaginary) = entry
606 .default
607 .map_or((0, 0.0, 0.0), |value| (1, value.re, value.im));
608 manifest.push_str(&format!(
609 "{name}\t{}\t{}\t{}\t{present}\t{real:.17e}\t{imaginary:.17e}\n",
610 format_name(entry.operator.format()),
611 shape.0,
612 shape.1,
613 ));
614 if entry.operator.format() == MatrixFormat::Dense {
615 archive
616 .start_file(format!("entries/{index}/matrix.npy"), options)
617 .map_err(archive_error)?;
618 write_complex_npy(&mut archive, shape.0, shape.1, &entry.operator.to_dense())?;
619 } else {
620 let triplets = entry.operator.triplets();
621 let rows = triplets
622 .iter()
623 .map(|(row, _, _)| u64::try_from(*row).map_err(archive_error))
624 .collect::<Result<Vec<_>>>()?;
625 let columns = triplets
626 .iter()
627 .map(|(_, column, _)| u64::try_from(*column).map_err(archive_error))
628 .collect::<Result<Vec<_>>>()?;
629 let values: Vec<_> = triplets.iter().map(|(_, _, value)| *value).collect();
630 archive
631 .start_file(format!("entries/{index}/rows.npy"), options)
632 .map_err(archive_error)?;
633 write_u64_npy(&mut archive, &rows)?;
634 archive
635 .start_file(format!("entries/{index}/columns.npy"), options)
636 .map_err(archive_error)?;
637 write_u64_npy(&mut archive, &columns)?;
638 archive
639 .start_file(format!("entries/{index}/values.npy"), options)
640 .map_err(archive_error)?;
641 write_complex_npy(&mut archive, values.len(), 1, &values)?;
642 }
643 }
644 archive
645 .start_file("manifest.tsv", options)
646 .map_err(archive_error)?;
647 archive
648 .write_all(manifest.as_bytes())
649 .map_err(archive_error)?;
650 archive
651 .start_file("metadata.tsv", options)
652 .map_err(archive_error)?;
653 for (key, value) in &entries.metadata {
654 archive
655 .write_all(format!("{key}\t{value}\n").as_bytes())
656 .map_err(archive_error)?;
657 }
658 archive.finish().map_err(archive_error)?;
659 Ok(())
660}
661
662pub fn load_zip(path: impl AsRef<Path>) -> Result<OperatorArchive> {
664 let file = File::open(path).map_err(archive_error)?;
665 let mut archive = ZipArchive::new(file).map_err(archive_error)?;
666 {
667 let mut version_entry = archive
668 .by_name("archive_version.npy")
669 .map_err(archive_error)?;
670 let version = read_version(&mut version_entry)?;
671 if version != ARCHIVE_VERSION {
672 return Err(QmbedError::Archive(format!(
673 "unsupported operator archive version {version}"
674 )));
675 }
676 }
677 let manifest = {
678 let mut entry = archive.by_name("manifest.tsv").map_err(archive_error)?;
679 let mut text = String::new();
680 entry.read_to_string(&mut text).map_err(archive_error)?;
681 text
682 };
683 let mut result = OperatorArchive::new();
684 let has_metadata = archive.file_names().any(|name| name == "metadata.tsv");
685 if has_metadata {
686 let mut entry = archive.by_name("metadata.tsv").map_err(archive_error)?;
687 let mut text = String::new();
688 entry.read_to_string(&mut text).map_err(archive_error)?;
689 for line in text.lines() {
690 let (key, value) = line
691 .split_once('\t')
692 .ok_or_else(|| QmbedError::Archive("invalid archive metadata row".into()))?;
693 result.insert_metadata(key, value)?;
694 }
695 }
696 for (index, line) in manifest.lines().enumerate() {
697 let fields: Vec<_> = line.split('\t').collect();
698 if fields.len() != 7 {
699 return Err(QmbedError::Archive("invalid archive manifest row".into()));
700 }
701 let format = parse_format(fields[1])?;
702 let rows = fields[2].parse::<usize>().map_err(archive_error)?;
703 let columns = fields[3].parse::<usize>().map_err(archive_error)?;
704 let default = match fields[4] {
705 "0" => None,
706 "1" => Some(Complex64::new(
707 fields[5].parse::<f64>().map_err(archive_error)?,
708 fields[6].parse::<f64>().map_err(archive_error)?,
709 )),
710 _ => return Err(QmbedError::Archive("invalid default marker".into())),
711 };
712 let operator = if format == MatrixFormat::Dense {
713 let mut matrix = archive
714 .by_name(&format!("entries/{index}/matrix.npy"))
715 .map_err(archive_error)?;
716 let (actual_rows, actual_columns, values) = read_complex_matrix(&mut matrix)?;
717 if (actual_rows, actual_columns) != (rows, columns) {
718 return Err(QmbedError::Archive(
719 "dense entry shape disagrees with the manifest".into(),
720 ));
721 }
722 Operator::from_dense(rows, columns, values)?
723 } else {
724 let row_indices = {
725 let mut entry = archive
726 .by_name(&format!("entries/{index}/rows.npy"))
727 .map_err(archive_error)?;
728 read_u64_vector(&mut entry)?
729 };
730 let column_indices = {
731 let mut entry = archive
732 .by_name(&format!("entries/{index}/columns.npy"))
733 .map_err(archive_error)?;
734 read_u64_vector(&mut entry)?
735 };
736 let values = {
737 let mut entry = archive
738 .by_name(&format!("entries/{index}/values.npy"))
739 .map_err(archive_error)?;
740 let (value_rows, value_columns, values) = read_complex_matrix(&mut entry)?;
741 if value_columns != 1 || value_rows != row_indices.len() {
742 return Err(QmbedError::Archive(
743 "sparse values shape disagrees with sparse indices".into(),
744 ));
745 }
746 values
747 };
748 if row_indices.len() != column_indices.len() || row_indices.len() != values.len() {
749 return Err(QmbedError::Archive(
750 "sparse archive arrays have unequal lengths".into(),
751 ));
752 }
753 let triplets = row_indices
754 .into_iter()
755 .zip(column_indices)
756 .zip(values)
757 .map(|((row, column), value)| {
758 Ok((
759 usize::try_from(row).map_err(archive_error)?,
760 usize::try_from(column).map_err(archive_error)?,
761 value,
762 ))
763 })
764 .collect::<Result<Vec<_>>>()?;
765 Operator::from_triplets(rows, columns, triplets, format)?
766 };
767 result.insert(fields[0], operator, default)?;
768 }
769 Ok(result)
770}