fix: Address multi-model architecture review findings#61
Merged
ericchansen merged 3 commits intomasterfrom Mar 19, 2026
Merged
Conversation
- Widen TSFF bounds: bond_k [-50,50], angle_k [-10,10] for negative FCs - Add torsion support: param vector, I/O, bounds, get_torsion() method - Extend observable types: add torsion_angle with 360-degree wraparound - Fix correlate_energies: include ea/eao in select_group_of_energies - Strict eigenvalue check: ValueError on multi-negative Hessians - Document scoring divergence and lost upstream gradient methods - Add 11 new tests covering all changes (153 pass, 8 skip, 1 xfail) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses review findings in Q2MM’s refactored multi-model architecture by extending TSFF parameter support (negative force constants), adding torsion parameters/observables end-to-end, tightening eigenvalue handling, and documenting optimizer/scoring differences vs upstream.
Changes:
- Extend ForceField parameterization: TSFF-friendly bounds (negative bond/angle k) and torsions included in param vector, bounds, lookup, and MM3/Tinker I/O.
- Add torsion-angle observable support in the scipy objective function with 360° wraparound residual handling.
- Fix/clarify legacy scoring behavior (energy correlation across all energy types) and make negative-eigenvalue handling strict-by-default with warnings instead of prints; update README/scipy optimizer documentation.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
q2mm/models/forcefield.py |
Adds torsions to the model (vector/bounds/lookup) and widens bounds for TSFF usage. |
q2mm/models/ff_io.py |
Imports/exports torsion components for MM3 .fld and Tinker .prm via legacy df params. |
q2mm/models/param.py |
Allows negative ranges for bf/af parameter types to support TSFF. |
q2mm/models/hessian.py |
Adds strict handling for multi-negative eigenvalues and switches prints to warnings. |
q2mm/linear_algebra.py |
Mirrors strict eigenvalue handling and warning behavior. |
q2mm/optimizers/objective.py |
Adds torsion-angle observable type and dihedral geometry helper; documents scoring differences. |
q2mm/optimizers/scoring.py |
Fixes energy grouping to include ea/eao for correlation/zero-referencing. |
q2mm/optimizers/scipy_opt.py |
Documents mapping from upstream gradient methods to scipy equivalents. |
test/test_models.py |
Adds tests for negative bounds, torsion vector/bounds/lookup, and MM3 torsion parsing. |
test/test_linear_algebra.py |
Adds tests for strict vs non-strict behavior on multi-negative eigenvalues. |
README.md |
Documents supported optimization methods and notes legacy preservation. |
Comments suppressed due to low confidence (2)
q2mm/linear_algebra.py:69
- If there are no negative eigenvalues,
neg_indicesis empty and theelsebranch will raise IndexError atneg_indices[0][0]. Consider explicitly handling the no-negative case (e.g., return unchanged eigenvalues, or raise a clear ValueError).
index_to_replace = np.argmin(eigenvalues)
else:
index_to_replace = neg_indices[0][0]
replaced_eigenvalues = copy.deepcopy(eigenvalues)
q2mm/models/hessian.py:143
- If there are no negative eigenvalues,
neg_indicesis empty and theelsebranch will raise IndexError atneg_indices[0][0]. Consider explicitly handling the no-negative case (e.g., return unchanged eigenvalues, or raise a clear ValueError).
index_to_replace = np.argmin(eigenvalues)
else:
index_to_replace = neg_indices[0][0]
replaced_eigenvalues = copy.deepcopy(eigenvalues)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
- Fix invert_ts_curvature ndarray.all() precedence bug (linear_algebra.py) - Guard replace_neg_eigenvalue against empty neg_indices (both copies) - Make atom_indices required for add_torsion_angle (was silently broken) - Fix reversed-torsion test to use asymmetric elements - Remove if-guard on test_mm3_loads_torsions (assert torsions loaded) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Address critical and medium findings from a multi-model code review (Claude Opus 4.6, GPT-5.4, Gemini 3 Pro) comparing the refactored codebase against upstream.
Changes
Critical Fixes
bond_kfrom[0, 50]to[-50, 50]andangle_kfrom[0, 10]to[-10, 10]-- negative force constants are essential for transition state force fieldsTorsionParamnow included in param vector, I/O (MM3 + Tinker), bounds, andget_torsion()lookup. Each Fourier component (V1/V2/V3) is a separate optimizable parametertorsion_angletoObjectiveFunctionwith proper 360-degree wraparound in residuals and a_dihedral_angle()geometry helperBug Fixes
correlate_energiesselect_group_of_energiesnow iterates["e", "eo", "ea", "eao"]instead of just["e", "eo"]-- absolute energies were never zero-referenced before scoringreplace_neg_eigenvaluenow raisesValueErroron multi-negative eigenvalue Hessians by default (strict=True), withwarnings.warninstead ofprintDocumentation
ObjectiveFunction(raw residuals) and legacyscoring.py(energy correlation + normalization)lstsq,lagrange,svd) and their scipy equivalents inscipy_opt.pydocstring + READMETesting
test_models.pyandtest_linear_algebra.pyFiles Changed (11)
q2mm/models/forcefield.py-- TSFF bounds, torsion in param vectorq2mm/models/ff_io.py-- Torsion extraction/export for MM3 + Tinkerq2mm/models/param.py-- Allow negative values forbf/afptypesq2mm/models/hessian.py-- Strict eigenvalue checkq2mm/linear_algebra.py-- Strict eigenvalue check (duplicate of hessian.py)q2mm/optimizers/objective.py-- Torsion observable, scoring docsq2mm/optimizers/scoring.py-- correlate_energies fixq2mm/optimizers/scipy_opt.py-- Lost methods documentationtest/test_models.py-- 6 torsion + 3 bounds teststest/test_linear_algebra.py-- 2 eigenvalue strictness testsREADME.md-- Optimization methods section