Skip to content

Stream Matrix Market files: a codec behind import/export sparse - #48

Merged
Claptar merged 2 commits into
mainfrom
feat/mtx-codec
Oct 15, 2026
Merged

Claptar merged 2 commits into
mainfrom
feat/mtx-codec

Conversation

@Claptar

@Claptar Claptar commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

Part 2 of 3 for #24. Stacked on #47.

import sparse built a Python tuple per entry, and export sparse read indices and data once per row. Both now go through a new streaming codec, formats/mtx.py, which reads and writes about 16 MB of text at a time. Its gzip-aware block I/O and vectorised formatting are in formats/textio.py.

Import makes two passes:

  1. The first counts the entries in each row and checks the whole file before the store is touched.
  2. The second appends the entries in CSR order. It writes directly when rows arrive in order, and goes through on-disk buckets (core/csr_build) when they don't.

Export reads indices and data once per block, and gzips when the output name ends in .gz.

User-visible changes (for the 0.8.0 CHANGELOG)

  • Dtypes: integer values stay integers (int32, or int64 when a value needs it), and real values are float64. All were float32 before, which loses counts above 2^24.
  • Refusals: symmetric, skew-symmetric, hermitian, complex and dense array files are refused. A symmetric file used to be read silently as half its matrix.
  • Duplicates are summed, as scipy does.
  • indptr widens to int64 past 2^31 entries, instead of overflowing.
  • Storage: HDF5 data is gzipped, and a parent group created on the way is tagged as a dict.
  • Export text: exported floats read back exactly, and whole numbers print as integers.

Tests

  • tests/test_mtx_codec.py checks against scipy's mmread as the oracle. It covers every field, both gzip and plain files, sorted and shuffled input, duplicates, every refusal, line endings, and that the file is read exactly twice.
  • test_formats.py now uses scipy as the oracle instead of the removed private reader.
  • New perf guards:
    • export sparse read calls stay flat as n_obs grows. This fails on the old code: 64× more calls over a 64× span.
    • import sparse write calls stay flat as n_obs grows.

🤖 Generated with Claude Code

`import sparse` built a Python tuple per entry and `export sparse` read
indices and data once per row. Both now go through formats/mtx, which
reads and writes a block of about 16 MB at a time:

- import makes two passes: the first counts entries per row and checks
  the whole file before the store is touched, the second appends in CSR
  order -- directly when rows arrive in order, through on-disk buckets
  (core/csr_build) when they do not. Gzipped files are read as such.
- export reads indices and data once per block, and gzips when the
  output ends in .gz.

User-visible changes:
- integer values stay integers (int32, int64 when needed) and real ones
  are float64; all were float32, which loses counts above 2^24;
- symmetric, skew-symmetric, hermitian, complex and dense files are
  refused; a symmetric file was silently read as half its matrix;
- duplicate entries are summed, as scipy does; indptr widens to int64
  past 2^31 entries instead of overflowing;
- HDF5 data is gzipped, and a parent group made on the way is a dict;
- exported floats read back exactly, and whole numbers print as ints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 15, 2026 21:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-15T21:59:08.065199Z 91b4c7e PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (py3.12)

1 344 tests  +52   1 344 ✅ +52   2m 3s ⏱️ - 3m 23s
    1 suites ± 0       0 💤 ± 0 
    1 files   ± 0       0 ❌ ± 0 

Results for commit 27bf873. ± Comparison against base commit 13c8c8f.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (py3.13)

1 344 tests  +52   1 344 ✅ +52   3m 10s ⏱️ - 1m 59s
    1 suites ± 0       0 💤 ± 0 
    1 files   ± 0       0 ❌ ± 0 

Results for commit 27bf873. ± Comparison against base commit 13c8c8f.

♻️ This comment has been updated with latest results.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 91b4c7ee05

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/adata/formats/sparse.py
Comment thread src/adata/formats/textio.py Outdated
Comment thread src/adata/formats/mtx.py Outdated
- Duplicate sums: the data dtype is chosen in pass 1, before the store
  is touched, but duplicates are summed only in pass 2. Two int32
  values whose sum is not int32 therefore failed after `import sparse
  --inplace` had removed the matrix it was replacing. Pass 1 now also
  bounds each row's total magnitude, which no summed entry can exceed,
  and a file that may hold duplicates is typed by that bound.
- The int64 limits: fromstring saturates instead of failing, so a value
  equal to INT64_MAX or INT64_MIN was refused as overflow. Such a block
  is now checked token by token, and only a real overflow is refused.
- The formatter: magnitudes are computed in uint64, so INT64_MIN (whose
  int64 abs is itself, and hung the digit loop) and uint64 values above
  INT64_MAX (which wrapped negative) are written exactly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Claptar
Claptar changed the base branch from refactor/mtx-groundwork to main October 15, 2026 22:24
@Claptar
Claptar merged commit ce19812 into main Oct 15, 2026
6 checks passed
@Claptar
Claptar deleted the feat/mtx-codec branch October 15, 2026 22:26
@Claptar Claptar mentioned this pull request Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants