|
13 | 13 | convert_raman_ints
|
14 | 14 |
|
15 | 15 | __all__ = ["hessian_pos", "vibration", "edipole", "equadrupole", "is_orb_min",
|
16 |
| - "lowest_eival_orb_hessian", "ir_spectrum", "raman_spectrum"] |
| 16 | + "lowest_eival_orb_hessian", "ir_spectrum", "raman_spectrum", |
| 17 | + "optimal_geometry"] |
17 | 18 |
|
18 | 19 | # This file contains functions to calculate the perturbation properties of systems.
|
19 | 20 |
|
@@ -317,6 +318,28 @@ def is_orb_min(qc: BaseQCCalc, threshold: float = -1e-3) -> bool:
|
317 | 318 | eival = lowest_eival_orb_hessian(qc)
|
318 | 319 | return bool(torch.all(eival > threshold))
|
319 | 320 |
|
| 321 | +def optimal_geometry(qc: BaseQCCalc, length_unit: Optional[str] = None) -> torch.Tensor: |
| 322 | + """ |
| 323 | + Compute the optimal atomic positions of the system. |
| 324 | +
|
| 325 | + Arguments |
| 326 | + --------- |
| 327 | + qc: BaseQCCalc |
| 328 | + Quantum Chemistry calculation that has run. |
| 329 | +
|
| 330 | + length_unit: str or None |
| 331 | + The returned unit. If ``None``, returns in atomic unit. |
| 332 | +
|
| 333 | + Returns |
| 334 | + ------- |
| 335 | + torch.Tensor |
| 336 | + Tensor with shape ``(natoms, ndim)`` represents the position |
| 337 | + of atoms at the optimal geometry. |
| 338 | + """ |
| 339 | + atompos = _optimal_geometry(qc) |
| 340 | + atompos = convert_length(atompos, to_unit=length_unit) |
| 341 | + return atompos |
| 342 | + |
320 | 343 | @memoize_method
|
321 | 344 | def _hessian_pos(qc: BaseQCCalc) -> torch.Tensor:
|
322 | 345 | # calculate the hessian in atomic unit
|
@@ -460,6 +483,28 @@ def _equadrupole(qc: BaseQCCalc) -> torch.Tensor:
|
460 | 483 |
|
461 | 484 | return quadrupole + ion_quadrupole
|
462 | 485 |
|
| 486 | +@memoize_method |
| 487 | +def _optimal_geometry(qc: BaseQCCalc) -> torch.Tensor: |
| 488 | + # calculate the optimal geometry |
| 489 | + system = qc.get_system() |
| 490 | + atompos = system.atompos |
| 491 | + |
| 492 | + # check if the atompos requires grad |
| 493 | + _check_differentiability(atompos, "atom positions", "optimal geometry") |
| 494 | + |
| 495 | + # get the energy for a given geometry |
| 496 | + def _get_energy(atompos: torch.Tensor) -> torch.Tensor: |
| 497 | + new_system = system.make_copy(moldesc=(system.atomzs, atompos)) |
| 498 | + new_qc = qc.__class__(new_system).run() |
| 499 | + ene = new_qc.energy() # calculate the energy |
| 500 | + return ene |
| 501 | + |
| 502 | + # get the minimal enery position |
| 503 | + minpos = xitorch.optimize.minimize(_get_energy, atompos, method="gd", maxiter=200, |
| 504 | + step=1e-2) |
| 505 | + |
| 506 | + return minpos |
| 507 | + |
463 | 508 | ########### helper functions ###########
|
464 | 509 |
|
465 | 510 | def _jac(a: torch.Tensor, b: torch.Tensor, create_graph: Optional[bool] = None,
|
|
0 commit comments