PLAN.md and SPEC.md were pre-implementation design docs for the pocket-grid feature. The feature has shipped, so they're frozen artifacts in the active todo/ namespace. Delete them and strip the three "see SPEC.md" comments that pointed at SPEC.md from Main.groovy and the predict/rescore routines. Also reassess the PyMOL rank-gap entry in the audit: P2Rank ranks pockets contiguously throughout the predict path and all in-tree loaders (except SiteHoundLoader), so the previously-listed "renderer ignores rank gaps" is cosmetic-only (empty objects in the Models panel for small pockets whose filled BitSet ended up empty). Downgrade to a parity nit under Inconsistencies; promote the PUResNet surfaceAtoms re-linking to the Top-5.
19 KiB
Post-2.5.1 Audit Findings
Punch-list captured from a 10-agent audit of all changes between tag 2.5.1
and develop (branch develop, ~218 commits, ~523 files). Each item is
grouped by severity. File paths are repo-relative; line numbers reflect
state at audit time and may drift.
This is a tech-debt / triage backlog — items here have not been fixed. Resolve items individually as the surrounding code is touched, or schedule focused cleanups.
Highest-priority bugs (real or near-real)
-
VoxelHashAssignercell-prune lower bound is too aggressive.src/main/groovy/cz/siret/prank/program/routines/predict/output/grid/assign/VoxelHashAssigner.java:58uses(di²+dj²+dk²)·spacing²as a lower bound that ignores the sub-cell offset ofq. Concrete miss with shipping defaults:q=(0.59,0,0),spacing=1.2,cutoff=2.5skips a cell whose true distance is ~2.17 Å. Silently breaks the "both assigners agree" invariant documented onPocketAssigner.java:23-26. Existing test (PocketGridBuilderTest.groovy:158) misses it because its single SAS point lands exactly on a lattice center. Fix: correct the lower bound (max(0, |di|*spacing − spacing/2)per axis), or drop the prune. -
Lazy-init race on singleton energy features.
MethylEnergyFeature.groovy:28-40,MethylEnergyCloud{,X,XFull,X2,X2Full}SF.groovy:67-80,AbstractProbeEnergyFeature.groovy:47-59,66.calculator/configare non-volatile fields on registry singletons; multiple worker threads can construct (and observe partially constructed) calculators.ConcurrencyTest.groovy:27-28constructs the calculator on the main thread before spawning threads, so the test does NOT cover the race it was meant to validate. Fix:volatile+ DCL, or do one-shot init under a synchronized guard inpreProcessProtein; update the concurrency test to construct under contention. -
Coulomb plumbing is dead code.
EnergyCalculator.getAtomChargealways returns 0 (src/main/groovy/cz/siret/prank/features/implementation/energy2/calc/EnergyCalculator.groovy:351-357).enableCoulomb,dielectricConstant,coulombConstantare structurally inert.testCationProbeIncludesBothLJAndCoulombTerms(EnergyCalculatorTest.groovy:194-209) is a false positive against its own name. Either wire charges, or rip the Coulomb path out. -
PUResNet
surfaceAtomscome from a separateStructure.src/main/groovy/cz/siret/prank/domain/loaders/pockets/PUResNetLoader.groovy:50,55. The Concavity fix (c143e0fa) only re-routed thePredictionbinding; PUResNet has the same identity-mismatch class still uncaught (atoms are from a sub-PDB, not fromqueryProtein). ConcavityLoader's owncutoutShellstill runs against the residue subset rather thanqueryProtein.exposedAtoms. Fix: re-link atoms by PDB serial againstqueryProtein.allAtoms.getByID, mirroringFPocketLoader.groovy:137. -
AhojUbsSiteParserwill crash on "older full" CSVs.src/main/groovy/cz/siret/prank/domain/loaders/AhojUbsSiteParser.groovy:46usespocket_classas the sole marker but then unconditionally readsrg/n_unp_pockets/n_unp_pockets_multichaininAhojSiteInfo.fromCsvRecord. Commons CSV throwsIllegalArgumentExceptionfor unmapped header names. Fix: addrecord.isMapped(name)guards, or pick a marker column guaranteed present in every variant. -
Ligands.loadLigandsFromSeparateFilesdoesn't filter cofactors.src/main/groovy/cz/siret/prank/domain/Ligands.groovy:140-198. Whenload_ligands_from_separate_files=true, the cofactor branch inProtein.loadStructure(Protein.groovy:574-592) adds cofactor atoms toproteinAtoms, but the loader has no.findAll { !loaderParams.isCofactor(it) }guard. The cofactor becomes an ignored Ligand with contact distance ≈ 0. Self-acknowledged as cosmetic indocumentation/cofactors.md:421-429and dev doc Known Limitation #1, but no test exercises it. -
Aromatic-probe energy cap is applied before the cosine switch.
EnergyCalculator.groovy:248-250clips pre-switch, then multiplies byneighbor.switchValueat line 291. May be intended, but contradicts inline doc andtestAromaticRingEnergyCap(EnergyCalculatorTest.groovy:132-146) doesn't exercise the divergence (single atom inside RON window). Decide per-atom vs per-point capping and align doc + test. -
Centroid-source divergence across pocket loaders. Pocketeer uses the server-supplied JSON
centroid; FPocket uses voronoi centerOfMass; Concavity/PUResNet use geometric centroid post-fix; Seq2Pocket/SwinSite usegridPoints.centroid/surfaceAtoms.centroid. No documented contract for what "centroid" means across loaders. Either document the policy or normalize to geometric. -
RescorePocketsRoutine.checkDatasethas an empty truthy branch.src/main/groovy/cz/siret/prank/program/routines/predict/RescorePocketsRoutine.groovy:48-56hasif (runFpocketAdHoc) { /* comment */ }— the check appears inverted. Re-verify intended semantics.
Inconsistencies (renames / strategy lookups / cross-format / code vs docs)
-
NewPymolRendererclass name is stale —NewPymolRenderer.groovy:28. Two distinct active classes (NewPymolRenderervsPymolRenderer). The "New" prefix predates a refactor. The cofactor block depends on a static method on the misnamed class (PymolRenderer.groovy:147). Rename. -
Assigner vs filler lookup styles disagree. Assigners go through
PocketAssignerRegistry; fillers use a hand-written switch (PocketGridBuilder.java:118-127) plus a hard-coded['morph_closing','none']list (Main.groovy:223). Two places to keep in sync per new filler. -
NoOpFillerreturns input BitSet without cloning,MorphologicalCloserclones. Mixed ownership semantics;PocketShapeFiller.java:30contract is silent. Risk of silent corruption if a future caller mutates the per-pocket BitSet underfill=none. -
SphericityDescriptordegenerate convention diverges —SphericityDescriptor.java:32,48. Empty pocket → 0.0, single-point pocket → 1.0. Volume / RadiusOfGyration / PrincipalMoments / NumGridPoints return 0 for both. Joining columns: one says "perfectly spherical", others say "empty". -
PocketGridPdbSidecar.writecalled by both renderers —PocketGridPymolRenderer.groovy:121+PocketGridChimeraXRenderer.groovy:157. When both are enabled, same sidecar PDB is written twice (identical content). -
CSV
NaN/INFhandling is unspecified.FormatteremitsNaN/∞; INT columns silently coerce NaN to 0 (TableExporter.groovy:142). Document indocumentation/export-pocket-descriptors.mdandexport-points.md, or pick a canonical sentinel. -
Sort-direction comment in
PrincipalMomentsDescriptor.java:96-101says "Sort descending." whileArrays.sortis ascending. Downstream indexing compensates; comment is misleading. -
Cofactor case-sensitivity mismatch.
CofactorHandler.parseOne(CofactorHandler.groovy:325-335) uppercases;Dataset.LigandDefinition.parse(Dataset.groovy:761-805) does not.Params.groovy:536-537doc claims case-sensitive while cofactors are normalized. -
distro/prank.batmisses JVM flags. Lines 11-13 set only the three--add-opens. Missing--enable-native-access=ALL-UNNAMEDand the Java-23+--sun-misc-unsafe-memory-access=allowblock thatdistro/prankandprank.shapply. zstd-jni native warnings + future-Java compatibility gap on Windows. -
atomRoleCache/atomChargeCachekeyed by BioJavaAtomidentity.EnergyCalculator.groovy:131-132. Atoms across proteins are distinct identities; cache never hits across proteins and grows monotonically — slow leak for long-running calculators. -
computeEnergyForPointreturnsList<Double>(boxed).EnergyCalculator.groovy:146-179. Boxing per neighbor per probe in the hot loop. LegacyLJEnergyCalculator.computeEnergyForPointreturnsdouble. -
Evaluation.closestPocket()ignoressite_eval_sas_pts_as_atoms.Evaluation.groovy:81doc says "considering DCA measure" but usessite.atoms.dist(p.centroid)unconditionally. Diverges from DCA semantics when the param is enabled. -
Evaluation.getStats()returnsLinkedHashMap(comment claims "keep insertion order"), but the immediate callerEvalResults.getStats()puts everything into aTreeMap(EvalResults.groovy:189) — insertion order is lost. Either drop the misleading comment or useLinkedHashMapdownstream. -
PyMOL pocket-grid renderer iterates
1..maxRank; ChimeraX iteratesperPocketBasenames.keySet().PocketGridPymolRenderer.groovy:167,201,242. Cosmetic-only: P2Rank ranks pockets contiguously (everypredict-path and in-tree loader exceptSiteHoundLoaderassigni++/rank++), and the sidecar PDB strips ranks whosefilledBitSet is empty. PyMOL therefore emits emptypocket_grid_N/pocket_vol_N/pocket_gauss_N/pocket_hull_Nobjects when the assigner produced no points for a small pocket — they render as invisible but clutter the Models panel. Mirror the ChimeraX iteration pattern (a3efd084) for parity; not a correctness fix. -
PyMOL grid
solvent_radius=0vs ChimeraX non-zero probe.PocketGridPymolRenderer.groovy:189-190vsPocketGridChimeraXRenderer.groovy:264.vis_pocket_grid_volume_radiusmeans different things to the two renderers. Documented in code; not in the param help. -
pocket=0(unassigned) points never reach the PDB sidecar.PocketGridPdbSidecar.java:56-60iteratesgrid.getPocketToPointIndices()only. When-pocket_grid_include_unassigned 1, CSV/Parquet has the rows but visualization silently drops them. Document or extend.
Doc / config drift
-
README badge stuck at 2.5.1.
README.md:11vsbuild.gradle:25(2.6.0-dev.9). Typo./make-disro.shatREADME.md:225. -
Removed flags/commands still referenced in docs:
documentation/random-examples.md:7,12,17,22— singular-conservation_dir(removed in 2.2, renamed to-conservation_dirs).documentation/hidden-commands.md:65—fasta-mask(actual:fasta-masked).documentation/hidden-commands.md:83—analyze reduce-to-chains(actual:transform reduce-to-chains).documentation/training-score-transformers.md:4—*_pockets.csv(actual:*_predictions.csv).
-
src/main/resources/help.txtis severely outdated. Lists only 5 commands;Main.groovy:621-645registers 12+. Typosdafault,comamnd. Doesn't mention any 2.6 loader rescorers despitedocumentation/rescoring.mddescribing 10. -
distro/config/default.groovy:128-130commentsignore_het_groupsas "considered cofactor". After-cofactorsshipped in 2.6, this is actively misleading. Same comment indistro/config/default_rescore.groovy:120-122. -
distro/config/default.groovyis missing new params:cofactors,cofactor_max_protein_dist,aa_mapping,vis_highlight_cofactors. -
documentation/cofactors.md:190,389say "logs an INFO warning"; code logsWARN(CofactorHandler.groovy:222,261).documentation/dev/cofactors.md:36,39also describe R11/R14 as "INFO" while line 69 of the same file documents the promotion to WARN. -
Params.groovy:536-537doc on cofactor matching is stale (claims case-sensitive — actually normalized). -
feature-setup.md:8still says "P2Rank version: 2.4"; collapsibles hardcodeP2Rank 2.3-dev.1. Line 120 says "providing custom tables is not implemented yet (as for P2Rank 2.4.2)" while the very next bullet describes thecsvfeature that does this. -
feature-setup.md:125typo{peorein_file_name}. -
documentation/aa-mapping.md:21typoommited, extra space before comma. -
documentation/readme.mdindex missescofactors.md,conservation.md,export-pocket-grid.md,export-pocket-descriptors.md. -
CI matrix is
17,21,25,26only (.github/workflows/develop.yml:23). README claims "Java 17 or later (tested up to Java 25)"; 18–20/22/23/24 not exercised; "tested up to Java 25" lags the now-present 26. -
Distribution switched temurin → oracle (
develop.yml:31, commit1997ab94). No doc note explains the choice. -
PocketDescriptor.java:29-31"fits in i32" contract is unenforced; a descriptor producing1e20for an INT column silently emitsInteger.MAX_VALUEor wraps. AddMath.toIntExactat the writer. -
PointExportData.create()doc says "(for predict mode)" (PointExportData.groovy:141) but it's also used byrescore(ModelBasedRescorer.groovy:97,168).
Stale comments / dead code
-
"Immutable calculator instance" comments in 6 energy feature files after the lazy-init refactor:
MethylEnergyFeature.groovy:22,MethylEnergyCloud{,X,XFull,X2,X2Full}SF.groovy:27/30. -
EnergyCalculatorConfig.roleRulesCSV+role-rules.csvresource are wired but read nowhere.EnergyCalculatorConfig.groovy:28,40,143,162-164.AtomRole.classifyis hardcoded. -
PocketShapeFiller.java:24andParams.groovy:896-897use the term "surface atom"; actual inputs are SAS points (pre-SAS-revision wording). -
LoaderParams.groovy:60-66commented-out copy constructor;:20-22staleTODO get rid of this global variableonignoreLigandsSwitch. -
References to gitignored
local/PLAN_COFACTORS*.mdin committed code:CofactorPipelineTest.groovy:38,CofactorAnalyzeTest.groovy:17,documentation/dev/cofactors.md:6. -
FPocketLoader.groovy:149:pocket.centroid = pocket.voronoiCenters.centerOfMassis dead because thegetCentroidoverride at line 42 always recomputes. -
FPocketLoader.groovy:155// probably not needed(years old);:142fpocket3 TODO. -
ConcavityLoader.groovy:74// TODO XXXis meaningless;:91///X correctsorting…typo log marker; the class is not@CompileStatic. -
PUResNetLoader.groovy:55truncated comment mid-sentence (// not all of them are necessarily on the surface but it s).:35parameter namedliganatedProteinbut expects queryProtein.:23-28javadoc is a stub. -
PocketeerLoaderTestmissingpredictionIsBoundToQueryProteinand empty-input tests (every other new loader test has both). -
PocketGridBuilder.java:97-99"monomorphic dispatch, JIT-friendly" comment is wrong — bimorphic with two impls registered. -
PredictionPair.groovy:61,78,107,109still usescriteriumafter class rename (commit2de315e9). -
Evaluation.groovy:484rank-sentinel comment says 0; actual sentinel is-1(PredictionPair.rankOfIdentifiedPocket). -
Evaluation.groovy:132comment "Clear SAS points cache" — actually clears per-site lazy lookup, not the EvalContext cache. -
MethylEnergyFeature.groovy:67-70dangling try/catch skeleton;:55commented alternative path. -
prank.sh:13-15commented-XX:+UseConcMarkSweepGC(removed in JDK 14). -
misc/development-notes.mdis down to a single 6-line note — merge intomisc/dev/technical-debt.mdor delete. -
distro/config/default_rescore.groovy:46//== FAETUREStypo. -
distro/prank.bat:14lastset "JAVA_OPTS=%JAVA_OPTS%"is a no-op. -
PocketGridChimeraXRenderer.groovy:81says PyMOL transparency is 0.7; actualPROTEIN_TRANSPARENCYinPocketGridPymolRenderer.groovy:76is 0.5. -
PocketGridBuilder.java:54-56meta-commentary aboutAtoms.union-vs-Atoms.joinchoice — reads as a leftover review note. -
VolsiteGridPointDescriptorTest.groovy:81comment claims testing the defaultpocket_grid_volsite_radius=4.0, but the test pinsRADIUS=4.0via@BeforeEach. -
PocketDescriptorsTest.groovy:110-112arithmetic in the comment (≈ sqrt(2)*5 ≈ 7.07) doesn't match the geometry (centroid at (4.5,4.5,0), max dist ≈ 6.36). Asserted ratio still passes. -
AbstractScalarPocketDescriptor.java:21-23comment says "both shipped descriptors are multi-column" — accurate today, will silently lie when a scalar grid-point descriptor is added.
Test-isolation gaps
-
CofactorIntegrationTest.groovy:40-43mutatesLoaderParams.ignoreLigandsSwitchin@BeforeAllwithout@Isolated/@ResourceLock("Params")and without saving/restoring. CompareCofactorPipelineTest.groovy:55-65andCofactorAnalyzeTest.groovy:32-44which do both correctly. -
LigandMatchingTestsnapshotsParams.INSTANCEbut is not@Isolated/@ResourceLock-annotated. -
VolsiteGridPointDescriptorTest/VolsiteSmoothGridPointDescriptorTestmutateParams.INSTANCEin@BeforeEach/@AfterEachwithout the project's standard@Isolated/@ResourceLock("Params")annotations. -
PocketDescriptorRegistry/PocketGridPointDescriptorRegistry—NamedRegistryHelper-backedLinkedHashMapis not synchronized. The publicregister/unregisteris exposed for tests but lacks a thread-safety note. If a futureregisterhappens during a feature extractor read, behavior is undefined.
Performance nits
-
computeEnergyForPointboxes doubles (above). -
EnergyCalculator.groovy:170config.probeParams[probe]EnumMap.getper neighbor per probe; hoist to a pre-sizedProbeParams[]indexed byprobeIdx. -
PocketGridBuilder.java:83Map<Integer, BitSet> pocketToPointIndicesuses boxedIntegerkeys while the rest of the file goes to lengths to avoid boxing (LongIntHashMap, primitiveint[]). UseIntObjectHashMap. -
MorphologicalCloser.fillclones empty rawShell (MorphologicalCloser.java:30); unnecessary, inconsistent withNoOpFiller. -
KdTreeAssigner.computeRawShelldedup branch is dead (KdTreeAssigner.java:41): every atom returned by the KD tree is inlatticeIndexby construction.
Top-5 if you only fix five things
- Fix
VoxelHashAssignercell-prune lower bound (or drop it and rely on the post-fetch distance check). Restores the assigner-strategy equivalence the docs promise. - Make energy-feature lazy-init actually thread-safe
(
MethylEnergyFeature,AbstractProbeEnergyFeature); fixConcurrencyTestto construct calculators under contention. - Guard
AhojSiteInfo.fromCsvRecordwithrecord.isMapped(...)for the newrg/n_unp_pockets[_multichain]columns, so the parser doesn't crash on older "full" CSVs. - Re-link
PUResNetLoader.surfaceAtomstoqueryProteinby PDB serial (mirrorFPocketLoader.groovy:137); same identity-mismatch class as the Concavity fix. - README/help.txt/
distro/prank.battrio: bump the version badge, fix the./make-disro.shtypo, regeneratehelp.txtto list current commands, and bring Windows launcher JVM flags up to parity with the Bash launchers.