Skip to content

feat: persist variant genomes with a deterministic ID - #255

Merged
ovesh merged 6 commits into
mainfrom
feat/variant-genome-id
Oct 8, 2026
Merged

ovesh merged 6 commits into
mainfrom
feat/variant-genome-id

Conversation

@ovesh

@ovesh ovesh commented Sep 30, 2026

Copy link
Copy Markdown
Contributor
  • variant genomes deterministic id calculated by hashing variants and genome config
  • persist hash components using data manager (1 file / variant genome)
  • make VariantGenome equality more strict. previously two variant sets on different chromosomes with the same start could compare unequal because the ordering was only by start attribute.

- variant genomes deterministic id calculated by hashing variants and
  genome config
- persist hash components using data manager (1 file / variant genome)
- make VariantGenome equality more strict. previously two variant sets
on different chromosomes with the same start could compare unequal
because the ordering was only by `start` attribute.

@s22chan s22chan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, small comments

Comment thread tests/test_variant_genome.py
key=attrgetter("chromosome", "start", "ref", "alt"))

@property
def id(self) -> str:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

should this be __hash__ or it should be separate from the magic function?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Has to stay separate. __hash__ contract requires immutable state. It's safer to leave it intentionally un-hashable.

ovesh added 2 commits October 1, 2026 16:11
https://releases.llvm.org/23.1.0/projects/libcxx/docs/ReleaseNotes.html
libcxx 23 release notes say:
> libc++ has dropped a lot of transitive includes

this presents itself as many errors like
src/genome_track_io.cpp:639:2: error: no member named 'throw_with_nested' in namespace 'std'
  639 |         GK_RETHROW("for '{}'", interval);
      |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The last successful build was with libcxx 22.1.8, failed one is with
23.1.2.
Comment thread src/gk_assert.h
#define __GK_ASSERT_H__

#include "defines.h"
#include <exception>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nice catch and sorry about that!

@gkaur gkaur 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.

Great work! Thanks @ovesh, this gives us the missing building block for tracking target sites on modified genomes: a deterministic ID for a VariantGenome, plus a way to persist and reload the underlying base genome + variants.

It would be great if we could document the expected lifecycle somewhere in the user/API docs:

  • create VariantGenome(base_genome, variants)
  • call save()
  • store the returned variant_genome_id alongside downstream records
  • later, call VariantGenome.load(id) with access to the same data manager.

The main thing that would also be helpful to mention is that the ID is not self-contained; GK can only reconstruct it if the backing variant_genome.<id>.txt.gz file exists, and the saved variants are canonicalized/normalized.

Another small future ergonomics thought: if downstream systems eventually want a single genome like string, we could expose a convention like mm39:<variant_genome_id>. Not essential for this PR, since storing base genome + variant genome ID separately should work fine.

Aside from the small gzip determinism nit I left inline, this looks great to me!!

:py:exc:`ValueError`
The stored file does not match `variant_genome_id`.
"""
path = gk_data.data_manager.get_file(cls._FILENAME_FORMAT.format(variant_genome_id))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Small suggestion: should VariantGenome.load() validate that variant_genome_id looks like the expected 64-char hex digest before formatting it into the data-manager filename? Not critical, but it would avoid odd filenames and give a clearer error for bad IDs.

Currently, if some one uses there calls:

VariantGenome.load("abc")
VariantGenome.load("../xyz")

GK would try to fetch files like:

variant_genome.abc.txt.gz
variant_genome.../xyz.txt.gz

And I'm not sure what the error would be!

Comment thread genome_kit/variant_genome.py Outdated
lines = [self.genome.config] + [v.as_variant_string() for v in self._canonical_variants()]
with open(path, "wb") as f:
# mtime=0 keeps the bytes deterministic so re-saving an equal genome matches the existing upload
f.write(gzip.compress("".join(line + "\n" for line in lines).encode(), mtime=0))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Small nit: save() assumes gzip output is fully deterministic once we set mtime=0, but (Claude told me) there’s still one gzip header byte (“OS” byte), that can vary when Python/zlib writes the gzip wrapper. Common Unix-like builds often produce the same value, so this probably isn’t affecting us today, but another runtime/platform could produce different raw bytes.

Easiest fix I think would be to normalize that byte after compressing, or write the gzip through gzip.GzipFile(..., filename="", mtime=0) so the header is stable. Not urgent, but cheap to fix :)

@ovesh
ovesh merged commit 1156b4c into main Oct 8, 2026
58 of 59 checks passed
@ovesh
ovesh deleted the feat/variant-genome-id branch October 8, 2026 23:41
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.

3 participants