Skip to content

Fixed width char datasets - #29

Merged
BenBrock merged 3 commits into
mainfrom
fixed-width-char-datasets
Aug 3, 2026
Merged

Fixed width char datasets#29
BenBrock merged 3 commits into
mainfrom
fixed-width-char-datasets

Conversation

@BenBrock

@BenBrock BenBrock commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

No description provided.

BenBrock and others added 3 commits August 3, 2026 00:02
A text aux component was written as a variable-length HDF5 string dataset
whatever it was, and binsparse_to_ssmc_problem guessed the class to rebuild
from the matrix id:

    use_cellstr = isfield(Problem, 'id') && Problem.id > 2776;

That rule is wrong in both directions.  Across the collection there are 247
text aux components: 239 char matrices, ten of which have id > 2776, and eight
cellstr, all of which have id <= 2776.  So every real cellstr came back as a
char matrix and every SNAP char matrix came back as a cellstr, and isequal
failed for 13 matrices.

The id was never what made this work for MM and RB either.  sswrite only sends
a cellstr to a single .txt when sscellstring(X) && id > 2776, which no matrix
in the collection satisfies; every real cellstr takes the generic iscell branch
and is written one element per file, and it is that structure, not the
heuristic, that round trips.

Let the HDF5 string datatype record the class instead, so a file says what it
holds and no side channel is needed:

    fixed-length (H5T_STR_NULLPAD)  <->  char matrix, m-by-n
    variable-length (H5T_VARIABLE)  <->  cellstr, m-by-1

A char matrix is rectangular, so each element of a fixed-length dataset holds
one complete row including the blanks MATLAB pads it with, and the datatype
size carries the width.  Nothing is deblanked on either side, which retires
strip_text_rows and its trick of keeping padding on the widest row so that
char() could recover the width.  A cellstr is ragged, so it stays
variable-length and needs no padding convention.

Padding is NUL rather than blank because HDF5 sizes a fixed-length string in
bytes while MATLAB sizes a char matrix in characters.  The two agree only for
ASCII; blank padding would widen any row holding a multi-byte character.
Stripping trailing NULs recovers a row's exact characters whatever it contains.
Trailing blanks are stripped as well when a foreign file declares
H5T_STR_SPACEPAD, the Fortran convention.

Text now converts through matlab_bsp_strings.h rather than mxArrayToString,
which goes by way of the local code page and replaces what it cannot represent
with SUB (0x1A).  That silently mangled every non-ASCII aux row, 65,409 of them
in Pajek/IMDB alone.  The same fix applies to the JSON descriptor in
binsparse_write.c, where it was corrupting Problem.notes for SNAP/wiki-RfA (en
dash) and Pajek/Journals (replacement character).

Text datasets are also chunked and deflated now.  A variable-length payload
lives on the global heap, which no dataset filter reaches, and costs a 16-byte
descriptor per element; Pajek/IMDB colname alone took 27.6 MB of a 58.2 MB
file.  The whole file is now 18.7 MB.

Every one of the 341 locally cached matrices round trips through sswrite and
ssread with isequal true, including all 13 that used to fail.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The chunk-and-deflate decision keyed off the element count, so a text dataset
of one very wide row was left uncompressed however large it was.  Threshold on
count * element_size instead, which covers both that case and the many-small-
elements one, and record what compression actually buys:

  SNAP/wiki-topcats pagenames  372.6 MB -> 19.2 MB   fixed-length
  Moqri/MISKnowledgeMap Abstract  3.25 MB -> 3.23 MB  variable-length

A fixed-length dataset compresses well, since blank padding is redundant.  A
variable-length one barely moves: HDF5 keeps the strings on the global heap,
which the filter pipeline does not reach, so only the 16-byte-per-element
descriptor array is filtered.  That is an HDF5 limitation rather than a choice
here, and it costs about 3.2 MB across the whole collection, which has only
eight cellstr aux components.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@BenBrock
BenBrock merged commit 3a89064 into main Aug 3, 2026
3 checks passed
@BenBrock
BenBrock deleted the fixed-width-char-datasets branch August 3, 2026 04:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant