Source code for dxtb._src.calculators.properties.moments.quad

# This file is part of dxtb.
#
# SPDX-Identifier: Apache-2.0
# Copyright (C) 2024 Grimme Group
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Properties: Quadrupole Moment
=============================

Analytical calculation of the traceless quadrupole moment.

This module serves more as a short-cut for the calculation in
:class:`~dxtb.calculator.Calculator`, hiding some implementation details.
"""

from __future__ import annotations

import torch
from tad_mctc.math import einsum

from dxtb._src.typing import Tensor

__all__ = ["quadrupole"]


[docs] def quadrupole( qat: Tensor, dpat: Tensor, qpat: Tensor, positions: Tensor ) -> Tensor: """ Analytical calculation of traceless electric quadrupole moment. Parameters ---------- qat : Tensor Atom-resolved monopolar charges. dpat : Tensor Atom-resolved dipolar charges. qpat : Tensor Atom-resolved quadrupolar charges. positions : Tensor Cartesian coordinates of all atoms (shape: ``(..., nat, 3)``). Returns ------- Tensor Traceless electric quadrupole moment. """ # TODO: Shape checks if qpat.shape[-1] == 9: # (..., nat, 9) -> (..., nat, 3, 3) qpat = qpat.view(*qpat.shape[:-1], 3, 3) # trace: (..., nat, 3, 3) -> (..., nat) tr = 0.5 * einsum("...ii->...", qpat) qpat = torch.stack( [ 1.5 * qpat[..., 0, 0] - tr, # xx 3 * qpat[..., 1, 0], # yx 1.5 * qpat[..., 1, 1] - tr, # yy 3 * qpat[..., 2, 0], # zx 3 * qpat[..., 2, 1], # zy 1.5 * qpat[..., 2, 2] - tr, # zz ], dim=-1, ) # This incorporates the electric quadrupole contribution from the # nuclei: Q_ij = ∑_k Z_k r_ki r_kj vec = einsum("...ij,...i->...ij", positions, qat) # temporary pv2d = positions * (vec + 2 * dpat) # Compute the atomic contributions to molecular quadrupole moment cart = torch.empty( (*positions.shape[:-1], 6), device=positions.device, dtype=positions.dtype, ) cart[..., 0] = pv2d[..., 0] cart[..., 1] = ( positions[..., 0] * (vec[..., 1] + dpat[..., 1]) + dpat[..., 0] * positions[..., 1] ) cart[..., 2] = pv2d[..., 1] cart[..., 3] = ( positions[..., 0] * (vec[..., 2] + dpat[..., 2]) + dpat[..., 0] * positions[..., 2] ) cart[..., 4] = ( positions[..., 1] * (vec[..., 2] + dpat[..., 2]) + dpat[..., 1] * positions[..., 2] ) cart[..., 5] = pv2d[..., 2] # Compute the trace and make the tensor traceless tr = 0.5 * (cart[..., 0] + cart[..., 2] + cart[..., 5]) cart[..., 0] = 1.5 * cart[..., 0] - tr cart[..., 1] *= 3.0 cart[..., 2] = 1.5 * cart[..., 2] - tr cart[..., 3] *= 3.0 cart[..., 4] *= 3.0 cart[..., 5] = 1.5 * cart[..., 5] - tr # sum up contributions return qpat.sum(dim=-2) + cart.sum(dim=-2)