Hessian Utilities¶
hessian
¶
Canonical location for Hessian and eigenvalue operations.
Consolidated from the former q2mm.linear_algebra module.
Implements eigenvalue manipulation methods from Limé & Norrby (J. Comput. Chem. 2015, 36, 1130, DOI:10.1002/jcc.23797):
- Method C (
replace_neg_eigenvalue): Force reaction coordinate eigenvalue to a large positive value. Simple but distorts the eigenspectrum. - Method D (
keep_natural_eigenvalue): Keep the natural (negative) eigenvalue — gives ~13× lower RMS error but may produce unstable FFs. - Method E (
hybrid_eigenvalue_pipeline): Run D first, detect problematic parameters (zero/negative force constants), lock those, and reoptimize with C.
Also provides the eigenmatrix training data pipeline:
transform_to_eigenmatrix: project a Hessian into an eigenvector basisextract_eigenmatrix_data: extract diagonal/off-diagonal training data
mass_weight_hessian
¶
Mass-weight (or un-weight) a Hessian matrix in place.
Multiplies each element H[i,j] by 1 / sqrt(m_i * m_j) where
m_i is the atomic mass of the atom owning Cartesian coordinate i.
When reverse is True, the operation is inverted.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hess
|
ndarray
|
|
required |
atoms
|
Sequence[str] | object
|
Element symbols ( |
required |
reverse
|
bool
|
If |
False
|
Source code in q2mm/models/hessian.py
mass_weight_force_constant
¶
mass_weight_force_constant(force_const: float, atoms: Sequence[str] | object, reverse: bool = False, rm: bool = False) -> float
Mass-weight a force constant.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
force_const
|
float
|
Force constant value. |
required |
atoms
|
Sequence[str] | object
|
Element symbols ( |
required |
reverse
|
bool
|
If |
False
|
rm
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
float
|
Mass-weighted (or un-weighted) force constant. |
Source code in q2mm/models/hessian.py
mass_weight_eigenvectors
¶
mass_weight_eigenvectors(evecs: ndarray, atoms: Sequence[str] | object, reverse: bool = False) -> None
Mass-weight (or un-weight) eigenvectors in place.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
evecs
|
ndarray
|
|
required |
atoms
|
Sequence[str] | object
|
Element symbols ( |
required |
reverse
|
bool
|
If |
False
|
Source code in q2mm/models/hessian.py
decompose
¶
Decomposes matrix into its eigenvalues and eigenvectors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
matrix
|
ndarray
|
Matrix to decompose, matrix must be square. |
required |
Returns:
| Type | Description |
|---|---|
(ndarray, ndarray)
|
(eigenvalues, eigenvectors) where eigenvalues
is of shape |
Source code in q2mm/models/hessian.py
replace_neg_eigenvalue
¶
replace_neg_eigenvalue(eigenvalues: ndarray, replace_with=1.0, zer_out_neg=False, units=KJMOLA, strict=True) -> ndarray
Replace the most negative eigenvalue to invert TS curvature (Method C).
Implements "Method C" from Limé & Norrby (J. Comput. Chem. 2015, 36, 1130, DOI:10.1002/jcc.23797): the reaction coordinate eigenvalue is forced to a large positive value so that the TS is treated as an energy minimum by the MM force field.
The default replacement is 1.0 Hartree/Bohr², converted to
kJ/mol/Ų via co.HESSIAN_CONVERSION (≈ 9376). This operates
on Cartesian Hessian eigenvalues — not mass-weighted ones.
Note: Limé & Norrby showed that "Method D" (fitting the natural eigenvalue without forced replacement) gives ~13× lower RMS error, but can produce unstable force fields. Their recommended "Method E" is a hybrid: use D first, lock problematic parameters, then reoptimize with C. See the paper for details and issue #75 for implementation status.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
eigenvalues
|
ndarray
|
Eigenvalues from Cartesian Hessian decomposition. |
required |
replace_with
|
float
|
Replacement value in Hartree/Bohr². Defaults to 1.0. |
1.0
|
zer_out_neg
|
bool
|
If True, zero out remaining negative eigenvalues after replacing the most negative. Defaults to False. |
False
|
units
|
int
|
Target units for the replacement. If |
KJMOLA
|
strict
|
bool
|
If True, raise ValueError when more than one negative eigenvalue is found (indicates a higher-order saddle point or corrupted Hessian). If False, proceed with a warning. Defaults to True. |
True
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Eigenvalues with most negative eigenvalue replaced and, if requested, |
ndarray
|
remaining negative values zeroed out. |
Raises:
| Type | Description |
|---|---|
ValueError
|
When strict is True and more than one negative eigenvalue is present. |
Source code in q2mm/models/hessian.py
reform_hessian
¶
Forms the Hessian matrix by multiplying the eigenvalues and eigenvectors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
eigenvalues
|
ndarray[float]
|
eigenvalues |
required |
eigenvectors
|
ndarray[float]
|
eigenvectors |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
np.ndarray: Hessian matrix |
Source code in q2mm/models/hessian.py
transform_to_eigenmatrix
¶
Project a Hessian into an eigenvector basis.
Computes eigenvectors.T @ hessian @ eigenvectors (using the
np.linalg.eigh convention where eigenvectors are columns).
When the eigenvectors come from the same Hessian the result is
diagonal (the eigenvalues). When they come from a different
Hessian (e.g. projecting an MM Hessian onto QM eigenvectors) the
off-diagonal elements measure how well the second Hessian reproduces
the first's mode structure.
This is the core operation behind the eigenmatrix training data
approach in Q2MM — see the -jeigz / -mjeig commands in
upstream calculate.py.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hessian
|
ndarray
|
|
required |
eigenvectors
|
ndarray
|
|
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Note
The legacy code used evec @ hess @ evec.T because Jaguar
stored eigenvectors as rows. With numpy's column convention
the equivalent is evec.T @ hess @ evec.
Both the Hessian and eigenvectors should be in the same unit
system (typically mass-weighted Hartree/Bohr² after calling
:func:mass_weight_hessian and :func:mass_weight_eigenvectors).
Source code in q2mm/models/hessian.py
extract_eigenmatrix_data
¶
extract_eigenmatrix_data(eigenmatrix: ndarray, *, diagonal_only: bool = False) -> list[tuple[int, int, float]]
Extract elements from an eigenmatrix as (row, col, value) tuples.
Returns the lower-triangular elements (including the diagonal) by
default, matching the legacy -mjeig command. Set
diagonal_only=True to return only diagonal elements (matching
-jeigz).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
eigenmatrix
|
ndarray
|
Square eigenmatrix from :func: |
required |
diagonal_only
|
bool
|
If True, return only diagonal elements. |
False
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, int, float]]
|
List of |
Source code in q2mm/models/hessian.py
invert_ts_curvature
¶
Invert the curvature of a TS Hessian matrix.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hessian_matrix
|
ndarray
|
Hessian matrix whose curvature to invert. |
required |
method
|
Literal['C', 'D']
|
Eigenvalue treatment method.
|
'C'
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Modified Hessian matrix. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If method is not |
Source code in q2mm/models/hessian.py
keep_natural_eigenvalue
¶
Return eigenvalues unchanged (Method D).
Method D from Limé & Norrby (J. Comput. Chem. 2015, 36, 1130): keeps the natural (negative) reaction coordinate eigenvalue during fitting. This avoids the large distortion introduced by Method C and produces ~13× lower RMS error, but the resulting force field may have zero or negative bending constants that lead to unphysical MM minima.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
eigenvalues
|
ndarray
|
Eigenvalues from Hessian decomposition. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Unmodified eigenvalues (a copy for safety). |
Source code in q2mm/models/hessian.py
detect_problematic_params
¶
Detect force field parameters with zero or negative force constants.
After fitting with Method D, some parameters may converge to physically unreasonable values. This function identifies them by their canonical key so they can be re-set for Method E re-optimization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
forcefield
|
ForceField
|
ForceField with estimated parameters. |
required |
fc_threshold
|
float
|
Force constants at or below this value are flagged. |
0.0
|
Returns:
| Type | Description |
|---|---|
dict[str, set[tuple]]
|
Dict with |
dict[str, set[tuple]]
|
of canonical parameter keys (element tuples from |
Source code in q2mm/models/hessian.py
lock_params
¶
Reset problematic parameters to values from a reference force field.
Copies force constant and equilibrium values from source_ff to forcefield for parameters whose keys appear in lock_keys.
Note: this only copies values — it does not prevent a subsequent
optimizer from overwriting them. To truly freeze parameters during
optimization, exclude them from the parameter vector (see
ForceField.get_param_vector / set_param_vector).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
forcefield
|
ForceField
|
ForceField to modify in-place. |
required |
lock_keys
|
dict[str, set[tuple]]
|
Dict from |
required |
source_ff
|
ForceField
|
Reference ForceField to copy values from (typically the Method D result or a standard force field). |
required |