Skip to content

Crystal-mates + cache meta-data - #88

Open
vratins wants to merge 5 commits into
mainfrom
dev_crystal_mates
Open

Crystal-mates + cache meta-data#88
vratins wants to merge 5 commits into
mainfrom
dev_crystal_mates

Conversation

@vratins

@vratins vratins commented Aug 4, 2026

Copy link
Copy Markdown
Contributor
  • Cleans up the symmetry mates code: per-atom and per-ligand dedup, is_mate emb_res_idx fields. Closes the special-position label leak, a water on a rotation axis is its own symmetry copy, i.e. the target sitting in the input.
  • Anchors the uniform-ball prior on ASU atoms only. No-mates runs unchanged.
  • Adds _filter_meta.json per cache dir; a run with different filter settings is refused instead of silently extending the cache.
  • Added tests for the above.

Default changes to match current checkpoint runs: max_bfactor_zscore 1.5 -> 2.0, min_water_residue_ratio 0.6 -> 0.1. Every cache used for training was built at the these values.

Summary by CodeRabbit

  • New Features

    • Improved handling of crystal symmetry mates, ligands, and duplicate atoms.
    • Added cache validation to detect mismatched filtering or graph settings.
    • Added provenance tracking for embedding rows and cached geometry.
    • Prevented mate waters from influencing labels and improved water sampling around eligible protein atoms.
  • Bug Fixes

    • Corrected embedding assignments for ligands, unmatched atoms, and symmetry mates.
    • Updated default quality-filter thresholds to retain more valid structures.
  • Documentation

    • Expanded guidance on symmetry mates, masks, cache behavior, and filtering.
    • Clarified that SLAE embedding generation is retained for reproducibility while ESM is primarily used.

Copilot AI lite review requested due to automatic review settings August 4, 2026 21:13
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The dataset now supports filtered protein and ligand symmetry mates, deduplicates mate entities, maps mate atoms to ASU embedding rows, validates cache filter metadata, and prevents mate atoms from anchoring generated waters. Filtering defaults and documentation were updated.

Changes

Symmetry-aware dataset preprocessing

Layer / File(s) Summary
Mate selection and deduplication
src/dataset.py, README.md, tests/conftest.py, tests/test_dataset.py
Crystal contacts now separate protein and ligand mates, exclude mate waters, deduplicate coincident atoms and entities, and include mate coordinates in water filtering.
Graph assembly, embeddings, and cache metadata
src/dataset.py, README.md, tests/test_dataset.py
Cached graphs now store is_mate and emb_res_idx. Mate nodes inherit ASU embeddings. Ligands and unmatched atoms receive zero embedding rows. Cache metadata records filter settings and rejects mismatches.
ASU-anchored water sampling
src/flow.py, tests/test_flow.py
Water sampling accepts an optional anchor mask. FlowMatcher excludes protein symmetry mates when mates are present.
Filtering defaults and supporting updates
scripts/inference.py, scripts/train.py, scripts/generate_slae_embeddings.py, src/constants.py, README.md, tests/conftest.py
Water-residue and B-factor defaults changed to 0.1 and 2.0. CLI help, encoder notes, constants documentation, and fixture notes were updated.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DatasetPreprocessor
  participant GeometryCache
  participant EmbeddingAnnotator
  participant FlowMatcher
  DatasetPreprocessor->>GeometryCache: write mate flags and embedding indices
  GeometryCache->>EmbeddingAnnotator: load cached graph metadata
  EmbeddingAnnotator->>EmbeddingAnnotator: inherit ASU rows for mates
  FlowMatcher->>FlowMatcher: exclude mate atoms from water anchors
Loading

Possibly related PRs

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the two main changes: crystal-mate handling and cache metadata validation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev_crystal_mates

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.

Pull request overview

This PR improves how crystal symmetry mates are handled in dataset preprocessing and sampling, adds cache provenance metadata to prevent mixing incompatible caches, and updates defaults/tests/docs to reflect the new behavior.

Changes:

  • Refactors crystal-mate collection to exclude mate waters, optionally include ligand mates, and deduplicate coincident mate atoms/ligands; adds is_mate and emb_res_idx provenance fields.
  • Anchors the uniform-ball water prior on ASU (non-mate) atoms when mates exist.
  • Writes _filter_meta.json into each geometry cache directory and refuses to read/extend caches built with different filter/graph settings; updates defaults and expands test coverage.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/test_flow.py Adds unit tests for anchor_mask behavior and per-graph anchoring semantics in uniform-ball sampling.
tests/test_dataset.py Adds extensive unit/integration tests for mate deduplication, residue-id parsing, mate/ligand separation, embedding inheritance, and cache filter sidecar behavior.
tests/conftest.py Updates PDB fixture documentation to note special-position water scenario for 4h0b.
src/flow.py Adds anchor_mask support to uniform-ball sampling and uses is_mate to anchor sampling on ASU atoms.
src/dataset.py Implements mate/ligand selection changes, deduplication utilities, is_mate/emb_res_idx fields, and _filter_meta.json cache provenance enforcement.
src/constants.py Removes outdated comment text about embedding dims (constants unchanged).
scripts/train.py Updates default thresholds and clarifies help text about cache-write-time filtering implications.
scripts/inference.py Updates default filter thresholds to match training/cache expectations.
scripts/generate_slae_embeddings.py Updates script note to reflect current usage status more accurately.
README.md Documents new mate-handling rules, node ordering/masks, embedding indexing, and cache provenance sidecar behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/flow.py
Comment on lines +734 to +737
is_mate = getattr(batch_data["protein"], "is_mate", None)
anchor_mask = (
~is_mate.bool() if is_mate is not None and bool(is_mate.any()) else None
)

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/flow.py (1)

105-111: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

The starvation fallback is global, not per graph.

If one graph in a batch has no eligible anchor, the mask is dropped for the whole batch. Every other graph then anchors on mate atoms too, which is the behaviour the mask exists to prevent. With is_mate from the dataset a starved graph means all-mate protein nodes, which is unlikely, so this is a batch-quality concern rather than a defect.

Consider falling back per graph: keep the mask where counts > 0 and re-enable all atoms only for the starved graphs. The docstring at lines 77-80 already states the current global behaviour, so update it if you change this.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/flow.py` around lines 105 - 111, Update the anchor filtering logic around
eligible and counts to apply starvation fallback per graph: retain eligible
anchors for graphs with counts greater than zero, while allowing all protein
atoms only for graphs with zero eligible anchors. Update the nearby docstring
describing global fallback to document the per-graph behavior, preserving
correct protein_pos and batch_p alignment.
src/dataset.py (1)

1267-1275: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider including mate-ligand coordinates in the distance-filter reference.

The reference adds crystal_data["mate_coords"] only. Mate ligand atoms become protein-type nodes later at lines 1464-1467, so a water that contacts only a mate ligand surface still fails max_protein_dist and is dropped, although its context atom is in the graph. That is the same asymmetry the comment says the mate coordinates exist to remove.

Note that the dedup pass has not run yet at this point, so the raw crystal_data["mate_ligand_coords"] would be the value to concatenate.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dataset.py` around lines 1267 - 1275, The quality-filter reference in the
include_mates branch must include both mate protein coordinates and raw
crystal_data["mate_ligand_coords"], so waters contacting mate ligands satisfy
max_protein_dist. Update the concatenation that builds filter_protein_coords
while preserving the existing non-mate path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/dataset.py`:
- Around line 1117-1129: Update the write branch around the sidecar creation so
it emits the existing missing-metadata warning before writing when geometry_dir
already contains .pt entries. Ensure this warning also occurs for preprocessing
runs that create FILTER_META_FILENAME, while preserving the atomic
temporary-file replacement and existing warning text.
- Around line 1405-1411: The mate-ligand grouping keys currently omit the
symmetry-object identity, allowing atoms from distinct operators to collapse.
Update dedup_mate_ligands_by_residue at src/dataset.py:312-322 and
mate_residue_keys at src/dataset.py:1405-1411 to include atom.model alongside
chain and resi, preserving the existing ordering and index-mapping behavior.

In `@tests/test_dataset.py`:
- Around line 2917-2919: Update the assertion near the mate-ordering test to
match the documented layout: verify that every node after the ASU boundary is
not necessarily a mate, while asserting the mate protein block is contiguous in
its expected range. Revise the misleading comment accordingly, and avoid using
the total mate count to slice across ASU ligand nodes.

---

Nitpick comments:
In `@src/dataset.py`:
- Around line 1267-1275: The quality-filter reference in the include_mates
branch must include both mate protein coordinates and raw
crystal_data["mate_ligand_coords"], so waters contacting mate ligands satisfy
max_protein_dist. Update the concatenation that builds filter_protein_coords
while preserving the existing non-mate path.

In `@src/flow.py`:
- Around line 105-111: Update the anchor filtering logic around eligible and
counts to apply starvation fallback per graph: retain eligible anchors for
graphs with counts greater than zero, while allowing all protein atoms only for
graphs with zero eligible anchors. Update the nearby docstring describing global
fallback to document the per-graph behavior, preserving correct protein_pos and
batch_p alignment.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5df00e45-9dfb-45a4-8de1-2cc84a420150

📥 Commits

Reviewing files that changed from the base of the PR and between b28dd6e and 4b2ba83.

📒 Files selected for processing (10)
  • README.md
  • scripts/generate_slae_embeddings.py
  • scripts/inference.py
  • scripts/train.py
  • src/constants.py
  • src/dataset.py
  • src/flow.py
  • tests/conftest.py
  • tests/test_dataset.py
  • tests/test_flow.py
💤 Files with no reviewable changes (1)
  • src/constants.py

Comment thread src/dataset.py
Comment on lines +1117 to +1129
if write:
self.geometry_dir.mkdir(parents=True, exist_ok=True)
# Written through a temp file: cache builds fan out over processes,
# and a reader must never catch a half-written sidecar.
tmp_path = meta_path.with_suffix(f".{os.getpid()}.tmp")
with open(tmp_path, "w") as f:
json.dump(current, f, indent=2)
tmp_path.replace(meta_path)
elif any(self.geometry_dir.glob("*.pt")):
logger.warning(
f"{self.geometry_dir} has no {FILTER_META_FILENAME}; the settings "
"its entries were built with cannot be verified."
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Warn when a preprocessing run stamps a directory that already holds entries.

The write branch runs whenever preprocess=True and no sidecar exists. If the directory already holds .pt files from a run made before the sidecar existed, this run writes its own settings over that unverified population. The sidecar then asserts a provenance nobody checked, and every later run trusts it. The warning branch is elif, so it never fires in this case.

Emit the same warning before claiming a non-empty unlabelled directory.

🛠️ Proposed fix
         if write:
+            if any(self.geometry_dir.glob("*.pt")):
+                logger.warning(
+                    f"{self.geometry_dir} already holds entries but no "
+                    f"{FILTER_META_FILENAME}; stamping it with this run's settings "
+                    "does not verify the entries already there."
+                )
             self.geometry_dir.mkdir(parents=True, exist_ok=True)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if write:
self.geometry_dir.mkdir(parents=True, exist_ok=True)
# Written through a temp file: cache builds fan out over processes,
# and a reader must never catch a half-written sidecar.
tmp_path = meta_path.with_suffix(f".{os.getpid()}.tmp")
with open(tmp_path, "w") as f:
json.dump(current, f, indent=2)
tmp_path.replace(meta_path)
elif any(self.geometry_dir.glob("*.pt")):
logger.warning(
f"{self.geometry_dir} has no {FILTER_META_FILENAME}; the settings "
"its entries were built with cannot be verified."
)
if write:
if any(self.geometry_dir.glob("*.pt")):
logger.warning(
f"{self.geometry_dir} already holds entries but no "
f"{FILTER_META_FILENAME}; stamping it with this run's settings "
"does not verify the entries already there."
)
self.geometry_dir.mkdir(parents=True, exist_ok=True)
# Written through a temp file: cache builds fan out over processes,
# and a reader must never catch a half-written sidecar.
tmp_path = meta_path.with_suffix(f".{os.getpid()}.tmp")
with open(tmp_path, "w") as f:
json.dump(current, f, indent=2)
tmp_path.replace(meta_path)
elif any(self.geometry_dir.glob("*.pt")):
logger.warning(
f"{self.geometry_dir} has no {FILTER_META_FILENAME}; the settings "
"its entries were built with cannot be verified."
)
🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 1121-1121: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(tmp_path, "w")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dataset.py` around lines 1117 - 1129, Update the write branch around the
sidecar creation so it emits the existing missing-metadata warning before
writing when geometry_dir already contains .pt entries. Ensure this warning also
occurs for preprocessing runs that create FILTER_META_FILENAME, while preserving
the atomic temporary-file replacement and existing warning text.

Comment thread src/dataset.py
Comment on lines +1405 to +1411
# compute mate residue indices (group atoms by actual residue)
mate_residue_keys = [(a.chain, a.resi) for a in mate_atoms]
unique_mate_res = list(dict.fromkeys(mate_residue_keys)) # keeps order
mate_res_map = {k: i for i, k in enumerate(unique_mate_res)}
mate_res_idx = torch.tensor(
[mate_res_map[k] for k in mate_residue_keys], dtype=torch.long
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm the attribute PyMOL exposes for an atom's source object, and list every
# place the repo keys mate entities so both sites are covered.
rg -nP --type=py -C4 'atom\.(chain|resi|segi|model)|\.resi\b' src/ | head -60

Repository: diff-use/WaterFlow

Length of output: 2262


🌐 Web query:

PyMOL cmd.get_model atom object attribute model symexp copy chain resi

💡 Result:

The PyMOL commands cmd.get_model and symexp serve distinct purposes and do not share the specific parameter list or attribute structure you described. cmd.get_model The cmd.get_model command returns a ChemPy 'Indexed' model object containing atom data from a selection [1][2]. It is used to access atom information directly in Python rather than through the iterate command [3][4]. Usage: cmd.get_model(selection="(all)", state=1) The resulting object contains a list of atom objects (e.g., atoms = cmd.get_model("all")). Each atom in the list (e.g., at = atoms.atom) possesses attributes that can be accessed via dot notation, including: - model: The object name [3][5][6] - chain: The chain identifier [3][4][6] - resi: The residue number (as a string) [3][4][6] - name: The atom name [3][4][6] - resn: The residue name [3][6] - coord: Coordinates (as a list/tuple) [3][4] - index: The internal atom index [3][4][6] - b: The B-factor [3][4] symexp The symexp command is a separate function used to generate crystallographic symmetry mates for a structure [7][8]. It does not return a model object with these specific attributes; instead, it creates new objects in the PyMOL scene based on symmetry operations within a defined cutoff [7][9][8]. Usage: symexp(prefix, object, selection, cutoff, segi=0, quiet=1) [9][10] In this context, 'copy' is not a parameter of symexp or get_model. If you are looking to copy atoms or objects, you might use the create command or specific selection-based operations [11][12]. The attributes you listed (chain, resi, etc.) are standard atom properties accessible via the model object returned by cmd.get_model, but they are not parameters of the symexp command [3][4][8].

Citations:


🌐 Web query:

Sybyx PyMOL symexp model attribute chain resi atom object name source

💡 Result:

In PyMOL, the symexp command is used to generate symmetry-related objects (crystal lattice partners) for a given structure [1][2]. The basic syntax is: symexp prefix, object, selection, cutoff [, segi] Arguments: - prefix: A string used as a name prefix for all newly created symmetry-related objects [2][3]. - object: The name of the existing PyMOL object to be replicated [2][4]. - selection: An atom selection used to determine the boundary for creating symmetry mates; only mates within the specified cutoff distance of this selection will be generated [2][5]. - cutoff: A float specifying the distance (in Angstroms) around the selection within which symmetry mates are created [2][3]. - segi (optional): If set to 1, assigns a unique 4-character segment identifier to each generated symmetry mate [2][3]. Naming Convention: Created objects are named using the pattern AAXXYYZZ [4]: - AA: A zero-based index for the symmetry operator. - XX, YY, ZZ: The unit cell offsets (-1, 0, or 1) along the X, Y, and Z axes, respectively [4]. Regarding your mention of "chain, resi, atom, object name, source": In PyMOL, these terms typically refer to properties used in atom selection expressions, rather than arguments for the symexp command itself [6][7]. When referencing atoms in PyMOL, you can use a selection macro syntax, such as /object_name/segi/chain/resi/name (where name corresponds to the atom name) [6]. The symexp command relies on an object name as its primary input (the source of the symmetry information) and an atom selection expression to define the spatial extent of the expansion [2][5].

Citations:


🏁 Script executed:

#!/bin/bash
# Inspect the surrounding implementation and any tests/fixtures related to mate handling.
set -euo pipefail

echo "== src/dataset.py relevant ranges =="
sed -n '260,350p' src/dataset.py
printf '\n'
sed -n '1350,1470p' src/dataset.py

echo
echo "== pytest references =="
rg -n --type=py -C3 'symexp|sym|mate_res_idx|dedup_mate_ligands_by_residue|ligand|image_frac' tests src/dataset.py | head -200

Repository: diff-use/WaterFlow

Length of output: 20714


🏁 Script executed:

#!/bin/bash
# Inspect relevant implementation and tests/fixtures related to mate handling.
set -euo pipefail

echo "== src/dataset.py relevant ranges =="
sed -n '260,350p' src/dataset.py
printf '\n'
sed -n '1350,1470p' src/dataset.py

echo
echo "== references =="
rg -n --type=py -C3 'symexp|sym|mate_res_idx|dedup_mate_ligands_by_residue|ligand|image_frac' tests src/dataset.py | head -220

Repository: diff-use/WaterFlow

Length of output: 22117


🏁 Script executed:

#!/bin/bash
# Check whether tests or helper code expose PyMOL symmetry-object models directly.
set -euo pipefail
printf '%s\n' '== files =='
git ls-files | sed -n '1,160p'
printf '\n== model/atom access in repo =='
rg -n --type=py -C3 'get_model|\.atom|model|symexp|cmd\.select|sym\*' .

Repository: diff-use/WaterFlow

Length of output: 50374


Use the PyMOL symmetry-object name as part of mate-entity IDs. get_model("sym* ...") returns atoms from all symmetry-prefixed objects, and atom chain/resi are not unique across those objects. Add atom.model to the mate ligand grouping key in dedup_mate_ligands_by_residue and to mate_residue_keys, so distinct operators do not collapse into one entity.

📍 Affects 1 file
  • src/dataset.py#L1405-L1411 (this comment)
  • src/dataset.py#L312-L322
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dataset.py` around lines 1405 - 1411, The mate-ligand grouping keys
currently omit the symmetry-object identity, allowing atoms from distinct
operators to collapse. Update dedup_mate_ligands_by_residue at
src/dataset.py:312-322 and mate_residue_keys at src/dataset.py:1405-1411 to
include atom.model alongside chain and resi, preserving the existing ordering
and index-mapping behavior.

Comment thread tests/test_dataset.py
Comment on lines +2917 to +2919
# Ligands ride behind the mates, so the mate block is contiguous but need
# not run to the end; every marked node is past the ASU.
assert is_mate[num_asu:][: is_mate.sum().item()].all()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

The mate block is not contiguous, so this assertion can fail.

The node order is [ASU protein | mate protein | ASU ligand | mate ligand], as src/dataset.py lines 1450-1491 and README.md lines 124-129 state. is_mate.sum() counts mate protein plus mate ligand atoms. When the structure has ASU ligands and include_ligands stays at its default True, the slice is_mate[num_asu:][: is_mate.sum()] spans the ASU-ligand nodes, which are False. The comment above the assertion also states the opposite of the documented layout.

Assert the property that actually holds: every mate node is past the ASU block, and the mate protein block is contiguous.

💚 Proposed test fix
-        # Ligands ride behind the mates, so the mate block is contiguous but need
-        # not run to the end; every marked node is past the ASU.
-        assert is_mate[num_asu:][: is_mate.sum().item()].all()
-
         mate_protein = is_mate & ~cached["is_ligand"]
+        # Node order is [ASU protein | mate protein | ASU ligand | mate ligand],
+        # so only the mate *protein* block is contiguous, right after the ASU.
+        assert mate_protein[:num_asu].sum().item() == 0
+        assert mate_protein[num_asu : num_asu + mate_protein.sum().item()].all()
         assert (emb_res_idx[mate_protein] >= 0).all()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_dataset.py` around lines 2917 - 2919, Update the assertion near
the mate-ordering test to match the documented layout: verify that every node
after the ASU boundary is not necessarily a mate, while asserting the mate
protein block is contiguous in its expected range. Revise the misleading comment
accordingly, and avoid using the total mate count to slice across ASU ligand
nodes.

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

I have some questions on the mates/ligands implementation. The connection between pymol handled mates/ligands and biotite handled ASU is in my opinion a brittle / risky point that can benefit from more real test cases and integration tests. In addition, before this PR, there is already code assumes or uses mates, and it is outside the diff for review. I would encourage double checking the correctness of what you added given the existing code (see the two major issues raised by coderabbitai).

Comment thread src/constants.py

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.

why removing the comments?

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.

I thought it was over-explanatory in this case, since we do mention this later in the code.

Comment thread README.md
Comment on lines 116 to +121
│ - protein_pos: centered protein coordinates (N, 3)
│ - protein_x: element one-hot encoding (N, 16)
│ - protein_res_idx: residue indices for grouping
│ - is_ligand: bool mask marking the appended ASU ligand atoms (N,)
│ - is_ligand: bool mask marking the ligand atoms (N,)
│ - is_mate: bool mask marking the symmetry-mate atoms (N,)
│ - emb_res_idx: embedding row per atom; -1 means no row (N,)

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.

you might want to rename protein_pos/x/res_idx to be more generic, maybe like node_pos/x/res_idx (referencing your line 124: Node order is [ASU protein | mate protein | ASU ligand | mate ligand]), since at this point they are not just protein atoms any more, and the array shape annotation N would get confusing.

Comment thread src/dataset.py
with open(tmp_path, "w") as f:
json.dump(current, f, indent=2)
tmp_path.replace(meta_path)
elif any(self.geometry_dir.glob("*.pt")):

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.

related to coderabbitai's comment. you probably want to move this elif branch up to be if the meta_path file does NOT exist, but *.pt exists, then log warning, regardless of whether write or not. This would change your test_unlabelled_cache_warns.

Comment thread src/dataset.py
key = (
str(sanitized_for_idx.chain_id[start]).strip(),
int(sanitized_for_idx.res_id[start]),
normalize_ins_code(sanitized_for_idx.ins_code[start]),

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.

seems not needed, already normalized in line 1329

Comment thread src/dataset.py
Comment on lines +1420 to +1422
asu_reskey_to_residx.get((str(atom.chain).strip(), *parsed), -1)
if parsed is not None
else -1

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.

in what scenario would you expect to have a non-matched mate protein reskey? Should here be a warning or raise an error? And what does a zero-embedding of those extra atoms functionally do to the water prediction?

Comment thread src/flow.py
batch_w: Tensor,
cutoff: float = 8.0,
device: torch.device | None = None,
anchor_mask: Tensor | None = None,

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 you add anchor_mask for sample_waters_scaled_gaussian too? would mate protein affect the sigma_per_graph computation?

Comment thread src/dataset.py
return mate_coords[keep_idx], [mate_atoms[i] for i in keep_idx]


def dedup_mate_ligands_by_residue(

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.

I am quite confused by the different treatment of mate protein vs ligand in the two dedup_* methods here. (1) Why are you not treating the mate protein in whole blocks (symmetry copy 1, copy 2, ...) like the whole ligand block? (2) Why does protein dedup needs first sweep + second round of neighbor look up, but ligand dedup only needs first sweep?

Comment thread tests/test_dataset.py


@pytest.mark.unit
class TestDedupMateAtoms:

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.

I think it's important for TestDedupMateAtoms and TestDedupMateLigandsByResidue to have a real test case where you see dedup is really needed.

Comment thread tests/test_dataset.py

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.

Currently you don't have integration tests where both ligands and mates are turned on. The ligand integration tests all have include_mates=False. The mates tests uses 6eey which doesn't contain ligands. You should add a test class/case. Also, I think most integration tests here uses encoder_type="gvp". If the paper uses esm, it's probably good to have at least one test here.

Comment thread tests/test_dataset.py
Comment on lines +2922 to +2923
assert (emb_res_idx[mate_protein] >= 0).all()
assert (emb_res_idx[mate_protein] < data["protein"].num_protein_residues).all()

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.

worth addressing the coderabbitai's comment above.
Also, here, asserting just the range is quite weak. Since getting the emb_res_idx wrong was what prevented symmetry mates from being useful in the past, I think a stronger test on exact equivalence (for residues that you know are the same in ASU vs in mate) would be helpful.

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