Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions docs/superpowers/follow-ups-after-4.0.0-remediation.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,22 @@ Verified against current source:
| `constants.py` `check_connectivity` hardcodes 1.25/1.1 | recheck; the surrounding code has moved |
| `isomers/parallel_embed.py` 138 lines | claim stands, but see the decision needed above |

The remaining nine entries were not re-checked and must be before anything is
deleted. This is the third time a manifest entry has turned out to be already
closed or wrong; the pattern is now reliable enough to treat every entry as a
claim rather than a fact.
**Full re-verification, 2026-08-03.** All 13 entries checked against source. The
list was **substantially wrong** — only 4 of 13 described code that was both
present and dead:

| verdict | entries |
|---|---|
| **already deleted** by earlier phases | `pad_molecular_batch`, `cli/progress` `create_progress`, `IsomerProgressCallback` |
| **not dead — M53 wrong** | `utils/stereo_check` (6 live uses), `cli/results` `FailedMolecule` + `print_failures` (live from `run.py` since the C6/C7 reconciliation work — the finding's "run.py admits failures is always []" no longer holds), `ASE/thermo` `mol2atoms`, `STANDARD_PRESSURE`, `isomers/parallel_embed` |
| **genuinely dead — deleted** | `utils_file.py` (whole module), `count_from_output`, `encode_smiles`, `decode_smiles`, `housekeeping_helper`, and 3 constants (`BOND_STRETCH_TOLERANCE`, `COLLISION_THRESHOLD`, `SUPPORTED_MODELS`) |
| **unresolved — line numbers stale** | `exceptions.py` "4 classes never raised" (line 41 is `OptimizationError`, raised 3x, so the cited lines no longer point at what the finding describes); `model_wrapper`'s legacy `name` API; `ASE/thermo` `model_name` param |

Net: **256 lines removed from `src/`, 167 from `tests/`** — not the ~450 of `src/`
the finding claimed, because a third of it was gone and half of the rest is alive.

The three unresolved entries need their own pass; the audit's line numbers cannot
be used to find them.

## What the remediation closed

Expand Down
12 changes: 0 additions & 12 deletions src/Auto3D/cli/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,18 +105,6 @@ def print_failures(failures: list[FailedMolecule], verbose: bool = False) -> Non
console.print("[dim]Run with -v to see details[/dim]")


def count_from_output(output_path: str) -> tuple[int, int]:
"""Return (unique_molecule_count, conformer_count) from an output SDF.

Thin back-compat wrapper around :func:`Auto3D.results.count_output` (the
single source of truth). ``main()`` now returns a ``WorkflowResult`` that
carries these counts, so the CLI reads them off the result instead.
"""
from Auto3D.results import count_output

return count_output(output_path)


def output_json(results: WorkflowResults) -> None:
"""Output results as JSON.

Expand Down
5 changes: 0 additions & 5 deletions src/Auto3D/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
HARTREE_TO_KCAL_PER_MOL = 627.50947337481 # 1 Hartree in kcal/mol

# Geometry thresholds
BOND_STRETCH_TOLERANCE = 1.25 # Maximum bond stretch factor
COLLISION_THRESHOLD = 1.1 # Å, minimum distance for clash detection
MIN_ATOM_DISTANCE = 0.9 # Å, minimum allowed interatomic distance

# Conformer generation limits
Expand Down Expand Up @@ -43,9 +41,6 @@
MODEL_ANI2XT = "ANI2xt"

# Supported model names (for validation)
SUPPORTED_MODELS = frozenset({MODEL_AIMNET, MODEL_ANI2X, MODEL_ANI2XT})

# Backward-compatible alias: "AIMNET" now maps to the aimnet registry default.
DEFAULT_AIMNET_MODEL = "aimnet2"
# Built-in (non-aimnet) engines kept for back-compat.
BUILTIN_ANI_MODELS = frozenset({MODEL_ANI2X.upper(), MODEL_ANI2XT.upper()})
Expand Down
6 changes: 0 additions & 6 deletions src/Auto3D/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,11 @@
combine_smi,
create_chunk_meta_names,
decode_ids,
decode_smiles,
encode_ids,
encode_smiles,
guess_file_type,
hash_enumerated_smi_IDs,
hash_taut_smi,
housekeeping,
housekeeping_helper,
reorder_sdf,
)
from Auto3D.utils.logging_config import configure_logging, get_logger
Expand Down Expand Up @@ -99,11 +96,8 @@
"check_valid_configuration",
# File operations
"guess_file_type",
"encode_smiles",
"decode_smiles",
"hash_enumerated_smi_IDs",
"hash_taut_smi",
"housekeeping_helper",
"housekeeping",
"create_chunk_meta_names",
"combine_smi",
Expand Down
148 changes: 0 additions & 148 deletions src/Auto3D/utils/file_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@
"""
from __future__ import annotations

import base64
import collections
import hashlib
import os
import shutil
from collections import defaultdict
Expand Down Expand Up @@ -176,135 +174,6 @@ def guess_file_type(filename: str) -> str:
return Path(filename).suffix[1:]


def encode_smiles(smiles: str, max_length: int = 50) -> str:
"""Encode a SMILES string for use in filenames.

Transforms a SMILES string into a filesystem-safe string by replacing
special characters with alphanumeric equivalents. For SMILES longer
than max_length, uses a hash-based encoding.

The encoding maps common SMILES characters to filename-safe alternatives:
- '=' -> 'd' (double bond)
- '#' -> 't' (triple bond)
- '@' -> 'a' (stereochemistry)
- '/' -> 's' (cis/trans)
- '\\' -> 'b' (cis/trans)
- '+' -> 'p' (positive charge)
- '-' -> 'm' (negative charge)
- '(' -> 'L' (left paren)
- ')' -> 'R' (right paren)
- '[' -> 'K' (left bracket)
- ']' -> 'J' (right bracket)
- '%' -> 'X' (ring number indicator)

Args:
smiles: The SMILES string to encode.
max_length: Maximum length for encoded string before using hash.
Defaults to 50 characters.

Returns:
A filesystem-safe encoded string representing the SMILES.

Example:
>>> encode_smiles("CCO")
'CCO'
>>> encode_smiles("C=C")
'CdC'
>>> encode_smiles("C#N")
'CtN'
>>> encode_smiles("[NH4+]")
'KNH4pJ'
"""
# Define character replacements for filesystem safety
# Using single lowercase letters that are unlikely to cause collisions
replacements = {
'=': 'd', # double bond
'#': 't', # triple bond
'@': 'a', # stereochemistry
'/': 's', # cis/trans
'\\': 'b', # cis/trans (backslash)
'+': 'p', # positive charge
'-': 'm', # negative charge (also single bond, but rare in SMILES)
'(': 'L', # left parenthesis
')': 'R', # right parenthesis
'[': 'K', # left bracket
']': 'J', # right bracket
'%': 'X', # ring number indicator (for rings > 9)
}

# Apply replacements
encoded = smiles
for char, replacement in replacements.items():
encoded = encoded.replace(char, replacement)

# If still too long, use a hash-based encoding
if len(encoded) > max_length:
# Use SHA256 hash truncated to produce a shorter, unique identifier
hash_obj = hashlib.sha256(smiles.encode('utf-8'))
# Take first 16 characters of base64-encoded hash (url-safe)
hash_str = base64.urlsafe_b64encode(hash_obj.digest()[:12]).decode('utf-8')
# Combine a prefix of the encoded SMILES with the hash
prefix_len = max_length - len(hash_str) - 1 # -1 for separator
if prefix_len > 0:
encoded = f"{encoded[:prefix_len]}_{hash_str}"
else:
encoded = hash_str

return encoded


def decode_smiles(encoded: str) -> str:
"""Decode an encoded SMILES string back to the original SMILES.

Reverses the encoding performed by encode_smiles for short SMILES strings.
Note: For hash-encoded (long) SMILES, the original cannot be recovered.

Args:
encoded: The encoded SMILES string.

Returns:
The decoded SMILES string. For hash-encoded strings, returns the
input unchanged since the original cannot be recovered.

Example:
>>> decode_smiles("CdC")
'C=C'
>>> decode_smiles("CtN")
'C#N'
>>> decode_smiles("KNH4pJ")
'[NH4+]'
"""
# Define reverse replacements
# Order matters: longer replacements should not interfere with shorter ones
replacements = {
'd': '=', # double bond
't': '#', # triple bond
'a': '@', # stereochemistry
's': '/', # cis/trans
'b': '\\', # cis/trans (backslash)
'p': '+', # positive charge
'm': '-', # negative charge
'L': '(', # left parenthesis
'R': ')', # right parenthesis
'K': '[', # left bracket
'J': ']', # right bracket
'X': '%', # ring number indicator
}

# Check if this looks like a hash-encoded string (contains underscore near end
# followed by base64-like characters)
if '_' in encoded and len(encoded.split('_')[-1]) >= 12:
# Likely hash-encoded, can't decode
return encoded

# Apply reverse replacements
decoded = encoded
for char, replacement in replacements.items():
decoded = decoded.replace(char, replacement)

return decoded


def hash_enumerated_smi_IDs(smi: str, out: str) -> None:
"""Write all SMILES with hashed IDs into a new file.

Expand Down Expand Up @@ -368,23 +237,6 @@ def hash_taut_smi(smi: str, out: str) -> None:
f.write(molecule)


def housekeeping_helper(folder: str, file: str) -> None:
"""Move a file into the specified folder.

Args:
folder: Destination folder path.
file: Path to the file to move.

Returns:
None. Moves the file to the destination folder.

Example:
>>> housekeeping_helper("/tmp/output", "/tmp/results.sdf")
"""
new_name = Path(folder) / Path(file).name
shutil.move(file, str(new_name))


def housekeeping(job_name: str, folder: str, optimized_structures: str) -> None:
"""Move this job directory's metadata files into a folder.

Expand Down
85 changes: 0 additions & 85 deletions src/Auto3D/utils_file.py

This file was deleted.

16 changes: 0 additions & 16 deletions tests/test_cli_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,19 +96,3 @@ def test_output_json():
output_json(results)


def test_count_from_output_counts_molecules_and_conformers(tmp_path):
from rdkit import Chem
from rdkit.Chem import AllChem
from Auto3D.cli.results import count_from_output

out = tmp_path / "out.sdf"
with Chem.SDWriter(str(out)) as w:
for name in ["a", "a", "b"]: # 2 unique ids, 3 conformers
m = Chem.AddHs(Chem.MolFromSmiles("CCO"))
AllChem.EmbedMolecule(m, randomSeed=1)
m.SetProp("_Name", name)
w.write(m)

molecules, conformers = count_from_output(str(out))
assert molecules == 2
assert conformers == 3
Loading
Loading