Source code for mdapy.steinhardt_bond_orientation

# Copyright (c) 2022-2024, mushroomfire in Beijing Institute of Technology
# This file is from the mdapy project, released under the BSD 3-Clause License.

import numpy as np
import taichi as ti

try:
    from tool_function import _check_repeat_cutoff, _check_repeat_nearest
    from replicate import Replicate
    from neighbor import Neighbor
    from nearest_neighbor import NearestNeighbor
    from box import init_box, _pbc, _pbc_rec
except Exception:
    from .tool_function import _check_repeat_cutoff, _check_repeat_nearest
    from .replicate import Replicate
    from .neighbor import Neighbor
    from .nearest_neighbor import NearestNeighbor
    from .box import init_box, _pbc, _pbc_rec


nfac_table_numpy = np.array(
    [
        1,
        1,
        2,
        6,
        24,
        120,
        720,
        5040,
        40320,
        362880,
        3628800,
        39916800,
        479001600,
        6227020800,
        87178291200,
        1307674368000,
        20922789888000,
        355687428096000,
        6.402373705728e15,
        1.21645100408832e17,
        2.43290200817664e18,
        5.10909421717094e19,
        1.12400072777761e21,
        2.5852016738885e22,
        6.20448401733239e23,
        1.5511210043331e25,
        4.03291461126606e26,
        1.08888694504184e28,
        3.04888344611714e29,
        8.8417619937397e30,
        2.65252859812191e32,
        8.22283865417792e33,
        2.63130836933694e35,
        8.68331761881189e36,
        2.95232799039604e38,
        1.03331479663861e40,
        3.71993326789901e41,
        1.37637530912263e43,
        5.23022617466601e44,
        2.03978820811974e46,
        8.15915283247898e47,
        3.34525266131638e49,
        1.40500611775288e51,
        6.04152630633738e52,
        2.65827157478845e54,
        1.1962222086548e56,
        5.50262215981209e57,
        2.58623241511168e59,
        1.24139155925361e61,
        6.08281864034268e62,
        3.04140932017134e64,
        1.55111875328738e66,
        8.06581751709439e67,
        4.27488328406003e69,
        2.30843697339241e71,
        1.26964033536583e73,
        7.10998587804863e74,
        4.05269195048772e76,
        2.35056133128288e78,
        1.3868311854569e80,
        8.32098711274139e81,
        5.07580213877225e83,
        3.14699732603879e85,
        1.98260831540444e87,
        1.26886932185884e89,
        8.24765059208247e90,
        5.44344939077443e92,
        3.64711109181887e94,
        2.48003554243683e96,
        1.71122452428141e98,
        1.19785716699699e100,
        8.50478588567862e101,
        6.12344583768861e103,
        4.47011546151268e105,
        3.30788544151939e107,
        2.48091408113954e109,
        1.88549470166605e111,
        1.45183092028286e113,
        1.13242811782063e115,
        8.94618213078297e116,
        7.15694570462638e118,
        5.79712602074737e120,
        4.75364333701284e122,
        3.94552396972066e124,
        3.31424013456535e126,
        2.81710411438055e128,
        2.42270953836727e130,
        2.10775729837953e132,
        1.85482642257398e134,
        1.65079551609085e136,
        1.48571596448176e138,
        1.3520015276784e140,
        1.24384140546413e142,
        1.15677250708164e144,
        1.08736615665674e146,
        1.03299784882391e148,
        9.91677934870949e149,
        9.61927596824821e151,
        9.42689044888324e153,
        9.33262154439441e155,
        9.33262154439441e157,
        9.42594775983835e159,
        9.61446671503512e161,
        9.90290071648618e163,
        1.02990167451456e166,
        1.08139675824029e168,
        1.14628056373471e170,
        1.22652020319614e172,
        1.32464181945183e174,
        1.44385958320249e176,
        1.58824554152274e178,
        1.76295255109024e180,
        1.97450685722107e182,
        2.23119274865981e184,
        2.54355973347219e186,
        2.92509369349301e188,
        3.3931086844519e190,
        3.96993716080872e192,
        4.68452584975429e194,
        5.5745857612076e196,
        6.68950291344912e198,
        8.09429852527344e200,
        9.8750442008336e202,
        1.21463043670253e205,
        1.50614174151114e207,
        1.88267717688893e209,
        2.37217324288005e211,
        3.01266001845766e213,
        3.8562048236258e215,
        4.97450422247729e217,
        6.46685548922047e219,
        8.47158069087882e221,
        1.118248651196e224,
        1.48727070609069e226,
        1.99294274616152e228,
        2.69047270731805e230,
        3.65904288195255e232,
        5.01288874827499e234,
        6.91778647261949e236,
        9.61572319694109e238,
        1.34620124757175e241,
        1.89814375907617e243,
        2.69536413788816e245,
        3.85437071718007e247,
        5.5502938327393e249,
        8.04792605747199e251,
        1.17499720439091e254,
        1.72724589045464e256,
        2.55632391787286e258,
        3.80892263763057e260,
        5.71338395644585e262,
        8.62720977423323e264,
        1.31133588568345e267,
        2.00634390509568e269,
        3.08976961384735e271,
        4.78914290146339e273,
        7.47106292628289e275,
        1.17295687942641e278,
        1.85327186949373e280,
        2.94670227249504e282,
        4.71472363599206e284,
        7.59070505394721e286,
        1.22969421873945e289,
        2.0044015765453e291,
        3.28721858553429e293,
        5.42391066613159e295,
        9.00369170577843e297,
        1.503616514865e300,
    ]
)


[docs] @ti.data_oriented class SteinhardtBondOrientation: """This class is used to calculate a set of bond-orientational order parameters :math:`Q_{\\ell}` to characterize the local orientational order in atomic structures. We first compute the local order parameters as averages of the spherical harmonics :math:`Y_{\ell m}` for each neighbor: .. math:: \\bar{Y}_{\\ell m} = \\frac{1}{nnn}\\sum_{j = 1}^{nnn} Y_{\\ell m}\\bigl( \\theta( {\\bf r}_{ij} ), \\phi( {\\bf r}_{ij} ) \\bigr), where the summation goes over the :math:`nnn` nearest neighbor and the :math:`\\theta` and the :math:`\\phi` are the azimuthal and polar angles. Then we can obtain a rotationally invariant non-negative amplitude by summing over all the components of degree :math:`l`: .. math:: Q_{\\ell} = \\sqrt{\\frac{4 \\pi}{2 \\ell + 1} \\sum_{m = -\\ell }^{m = \\ell } \\bar{Y}_{\\ell m} \\bar{Y}^*_{\\ell m}}. For a FCC lattice with :math:`nnn=12`, :math:`Q_4 = \\sqrt{\\frac{7}{192}} \\approx 0.19094`. More numerical values for commonly encountered high-symmetry structures are listed in Table 1 of `J. Chem. Phys. 138, 044501 (2013) <https://aip.scitation.org/doi/abs/10.1063/1.4774084>`_, and all data can be reproduced by this class. If :math:`wlflag` is True, this class will compute the third-order invariants :math:`W_{\\ell}` for the same degrees as for the :math:`Q_{\\ell}` parameters: .. math:: W_{\\ell} = \\sum \\limits_{m_1 + m_2 + m_3 = 0} \\begin{pmatrix}\\ell & \\ell & \\ell \\\m_1 & m_2 & m_3\\end{pmatrix}\\bar{Y}_{\\ell m_1} \\bar{Y}_{\\ell m_2} \\bar{Y}_{\\ell m_3}. For FCC lattice with :math:`nnn=12`, :math:`W_4 = -\\sqrt{\\frac{14}{143}} \\left(\\frac{49}{4096}\\right) \\pi^{-3/2} \\approx -0.0006722136`. If :math:`wlhatflag` is true, the normalized third-order invariants :math:`\\hat{W}_{\\ell}` will be computed: .. math:: \\hat{W}_{\\ell} = \\frac{\\sum \\limits_{m_1 + m_2 + m_3 = 0} \\begin{pmatrix}\\ell & \\ell & \\ell \\\m_1 & m_2 & m_3\\end{pmatrix}\\bar{Y}_{\\ell m_1} \\bar{Y}_{\\ell m_2} \\bar{Y}_{\\ell m_3}}{\\left(\\sum \\limits_{m=-l}^{l} |\\bar{Y}_{\ell m}|^2 \\right)^{3/2}}. For FCC lattice with :math:`nnn=12`, :math:`\\hat{W}_4 = -\\frac{7}{3} \\sqrt{\\frac{2}{429}} \\approx -0.159317`. More numerical values of :math:`\\hat{W}_{\\ell}` can be found in Table 1 of `Phys. Rev. B 28, 784 <https://doi.org/10.1103/PhysRevB.28.784>`_, and all data can be reproduced by this class. .. hint:: If you use this class in your publication, you should cite the original paper: `Steinhardt P J, Nelson D R, Ronchetti M. Bond-orientational order in liquids and glasses[J]. Physical Review B, 1983, 28(2): 784. <https://doi.org/10.1103/PhysRevB.28.784>`_ .. note:: This class is translated from that in `LAMMPS <https://docs.lammps.org/compute_orientorder_atom.html>`_. We also further implement the bond order to identify the solid or liquid state for lattice structure. For FCC structure, one can compute the normalized cross product: .. math:: s_\\ell(i,j) = \\frac{4\\pi}{2\\ell + 1} \\frac{\\sum_{m=-\\ell}^{\\ell} \\bar{Y}_{\\ell m}(i) \\bar{Y}_{\\ell m}^*(j)}{Q_\\ell(i) Q_\\ell(j)}. According to `J. Chem. Phys. 133, 244115 (2010) <https://doi.org/10.1063/1.3506838>`_, when :math:`s_6(i, j)` is larger than a threshold value (typically 0.7), the bond is regarded as a solid bond. Id the number of solid bond is larger than a threshold (6-8), the atom is considered as solid phase. .. hint:: If you use `identifySolidLiquid` function in this class in your publication, you should cite the original paper: `Filion L, Hermes M, Ni R, et al. Crystal nucleation of hard spheres using molecular dynamics, umbrella sampling, and forward flux sampling: A comparison of simulation techniques[J]. The Journal of chemical physics, 2010, 133(24): 244115. <https://doi.org/10.1063/1.3506838>`_ Args: pos (np.ndarray): (:math:`N_p, 3`) particles positions. box (np.ndarray): (:math:`3, 2`) system box, must be rectangle. boundary (list, optional): boundary conditions, 1 is periodic and 0 is free boundary. Defaults to [1, 1, 1]. verlet_list (np.ndarray, optional): (:math:`N_p, max\_neigh`) verlet_list[i, j] means j atom is a neighbor of i atom if j > -1. Defaults to None. distance_list (np.ndarray, optional): (:math:`N_p, max\_neigh`) distance_list[i, j] means distance between i and j atom. Defaults to None. neighbor_number (np.ndarray, optional): (:math:`N_p`) neighbor atoms number. Defaults to None. rc (float, optional): cutoff distance to find neighbors. Defaults to 0.0. qlist (list|int, optional): the list of order parameters to be computed, which should be a non-negative integer. Defaults to np.array([4, 6, 8, 10, 12], int). nnn (int, optional): the number of nearest neighbors used to calculate :math:`Q_{\ell}`. If :math:`nnn > 0`, the :math:`rc` has no effects, otherwise the summation will go over all neighbors within :math:`rc`. Defaults to 12. wlflag (bool, optional): whether calculate the third-order invariants :math:`W_{\ell}`. Defaults to False. wlhatflag (bool, optional): whether calculate the normalized third-order invariants :math:`\hat{W}_{\ell}`. If :math:`wlflag` is False, this parameter has no effect. Defaults to False. max_neigh (int, optional): a given maximum neighbor number per atoms. Defaults to 60. Outputs: - **qnarray** (np.ndarray) - (math:`N_p, len(qlist)*(1+wlflag+wlhatflag)`) consider the :math:`qlist=[4, 6]` and :math:`wlflag` and :math:`wlhatflag` is True, the columns of :math:`qnarray` are [:math:`Q_4, Q_6, W_4, W_6, \hat{W}_4, \hat{W}_6`]. - **solidliquid** (np.ndarray) - (math:`N_p`), 1 indicates solid state and 0 indicates liquid state. Examples: >>> import mdapy as mp >>> mp.init() >>> FCC = mp.LatticeMaker(3.615, 'FCC', 10, 10, 10) # Create a FCC structure >>> FCC.compute() # Get atom positions >>> BO = SteinhardtBondOrientation( FCC.pos, FCC.box, [1, 1, 1], None, None, None, 0.0, [4, 6, 8, 10, 12], 12, wlflag=False, wlhatflag=False, ) # Initialize BondOrder class >>> BO.compute() # Do the BondOrder computation. >>> BO.qnarray[0] # Check qnarray, it should be [0.19094067 0.57452428 0.40391458 0.01285704 0.60008306]. >>> BO.identifySolidLiquid() # Identify solid/liquid state. >>> BO.solidliquid[0] # Should be 1, that is solid. """ def __init__( self, pos, box, boundary=[1, 1, 1], verlet_list=None, distance_list=None, neighbor_number=None, rc=0.0, qlist=np.array([4, 6, 8, 10, 12], int), nnn=12, wlflag=False, wlhatflag=False, max_neigh=60, ): self.rc = rc self.nnn = nnn if self.nnn > 0: self.rc = 1000000000.0 repeat = _check_repeat_nearest(pos, box, boundary) else: assert self.rc > 0 repeat = _check_repeat_cutoff(box, boundary, self.rc) if pos.dtype != np.float64: pos = pos.astype(np.float64) box, inverse_box, rec = init_box(box) self.old_N = None if sum(repeat) == 3: self.pos = pos self.box, self.inverse_box, self.rec = box, inverse_box, rec else: self.old_N = pos.shape[0] repli = Replicate(pos, box, *repeat) repli.compute() self.pos = repli.pos self.box, self.inverse_box, self.rec = init_box(repli.box) self.box_length = ti.Vector([np.linalg.norm(self.box[i]) for i in range(3)]) self.boundary = ti.Vector([int(boundary[i]) for i in range(3)]) self.verlet_list = verlet_list self.distance_list = distance_list self.neighbor_number = neighbor_number if isinstance(qlist, int): self.qlist = np.array([qlist], int) elif isinstance(qlist, list): self.qlist = np.array(qlist, int) elif isinstance(qlist, tuple): self.qlist = np.array(qlist, int) elif isinstance(qlist, np.ndarray): self.qlist = qlist.astype(int) else: raise "qlist should be a non-negative integer or List[int]|Tuple[int]|np.array[int]." for i in self.qlist: assert ( i >= 0 ), "qlist should be a non-negative integer or List[int]|Tuple[int]|np.array[int]." self.wlflag = wlflag self.wlhatflag = wlhatflag self.nqlist = self.qlist.shape[0] ncol = self.qlist.shape[0] if self.wlflag: ncol += self.nqlist if self.wlhatflag: ncol += self.nqlist self.ncol = ncol self.nfac_table = ti.field(dtype=ti.f64, shape=nfac_table_numpy.shape) self.nfac_table.from_numpy(nfac_table_numpy) self.if_compute = False self.max_neigh = max_neigh @ti.func def _factorial(self, n: int) -> ti.f64: # if n >= 0 and n < self.nfac_table.shape[0]: return self.nfac_table[n] @ti.kernel def _init_clebsch_gordan( self, cglist: ti.types.ndarray(), qlist: ti.types.ndarray() ): idxcg_count = 0 for il in range(self.nqlist): l = qlist[il] for m1 in range(2 * l + 1): aa2 = m1 - l for m2 in range(ti.max(0, l - m1), ti.min(2 * l + 1, 3 * l - m1 + 1)): bb2 = m2 - l m = aa2 + bb2 + l sums = ti.f64(0.0) for z in range( ti.max(0, ti.max(-aa2, bb2)), ti.min(l, ti.min(l - aa2, l + bb2)) + 1, ): ifac = 1 if z % 2: ifac = -1 sums += ifac / ( self._factorial(z) * self._factorial(l - z) * self._factorial(l - aa2 - z) * self._factorial(l + bb2 - z) * self._factorial(aa2 + z) * self._factorial(-bb2 + z) ) cc2 = m - l sfaccg = ti.sqrt( self._factorial(l + aa2) * self._factorial(l - aa2) * self._factorial(l + bb2) * self._factorial(l - bb2) * self._factorial(l + cc2) * self._factorial(l - cc2) * (2 * l + 1) ) sfac1 = self._factorial(3 * l + 1) sfac2 = self._factorial(l) dcg = ti.sqrt(sfac2 * sfac2 * sfac2 / sfac1) cglist[idxcg_count] = sums * dcg * sfaccg idxcg_count += 1 @ti.func def _associated_legendre(self, l: int, m: int, x: float) -> float: res = ti.f64(0.0) if l >= m: p, pm1, pm2 = ti.f64(1.0), ti.f64(0.0), ti.f64(0.0) if m != 0: sqx = ti.sqrt(1.0 - x * x) for i in range(1, m + 1): p *= (2 * i - 1) * sqx for i in range(m + 1, l + 1): pm2 = pm1 pm1 = p p = ((2 * i - 1) * x * pm1 - (i + m - 1) * pm2) / (i - m) res = p return res @ti.func def _polar_prefactor(self, l: int, m: int, costheta: float) -> float: mabs = ti.abs(m) prefactor = ti.f64(1.0) for i in range(l - mabs + 1, l + mabs + 1): prefactor *= i prefactor = ti.sqrt( (2 * l + 1) / (4 * ti.math.pi * prefactor) ) * self._associated_legendre(l, mabs, costheta) if (m < 0) and (m % 2): prefactor = -prefactor return prefactor @ti.kernel def _get_idx(self, qlist: ti.types.ndarray()) -> int: idxcg_count = 0 ti.loop_config(serialize=True) for il in range(self.nqlist): l = qlist[il] for m1 in range(2 * l + 1): for _ in range(ti.max(0, l - m1), ti.min(2 * l + 1, 3 * l - m1 + 1)): idxcg_count += 1 return idxcg_count @ti.kernel def _compute( self, pos: ti.types.ndarray(dtype=ti.math.vec3), box: ti.types.ndarray(element_dim=1), verlet_list: ti.types.ndarray(), distance_list: ti.types.ndarray(), neighbor_number: ti.types.ndarray(), qlist: ti.types.ndarray(), qnm_r: ti.types.ndarray(), qnm_i: ti.types.ndarray(), qnarray: ti.types.ndarray(), cglist: ti.types.ndarray(), inverse_box: ti.types.ndarray(element_dim=1), ): MY_EPSILON = 2.220446049250313e-15 N = pos.shape[0] nqlist = self.nqlist K = 0 for i in range(N): nneigh = 0 # Make sure only iterate the nnn neighbors! if self.nnn > 0: K = self.nnn else: K = neighbor_number[i] for jj in range(K): j = verlet_list[i, jj] r = pos[i] - pos[j] if ti.static(self.rec): r = _pbc_rec(r, self.boundary, self.box_length) else: r = _pbc(r, self.boundary, box, inverse_box) rmag = distance_list[i, jj] if rmag > MY_EPSILON and rmag <= self.rc: nneigh += 1 costheta = r[2] / rmag expphi_r = r[0] expphi_i = r[1] rxymag = ti.sqrt(expphi_r * expphi_r + expphi_i * expphi_i) if rxymag <= MY_EPSILON: expphi_r = 1.0 expphi_i = 0.0 else: rxymaginv = 1.0 / rxymag expphi_r *= rxymaginv expphi_i *= rxymaginv for il in range(nqlist): l = qlist[il] qnm_r[i, il, l] += self._polar_prefactor(l, 0, costheta) expphim_r = expphi_r expphim_i = expphi_i for m in range(1, l + 1): prefactor = self._polar_prefactor(l, m, costheta) c_r = prefactor * expphim_r c_i = prefactor * expphim_i qnm_r[i, il, m + l] += c_r qnm_i[i, il, m + l] += c_i if m & 1: qnm_r[i, il, -m + l] -= c_r qnm_i[i, il, -m + l] += c_i else: qnm_r[i, il, -m + l] += c_r qnm_i[i, il, -m + l] -= c_i tmp_r = expphim_r * expphi_r - expphim_i * expphi_i tmp_i = expphim_r * expphi_i + expphim_i * expphi_r expphim_r = tmp_r expphim_i = tmp_i facn = 1.0 / nneigh for il in range(nqlist): l = qlist[il] for m in range(2 * l + 1): qnm_r[i, il, m] *= facn qnm_i[i, il, m] *= facn for il in range(nqlist): l = qlist[il] qnormfac = ti.sqrt(4 * ti.math.pi / (2 * l + 1)) qm_sum = ti.f64(0.0) for m in range(2 * l + 1): qm_sum += ( qnm_r[i, il, m] * qnm_r[i, il, m] + qnm_i[i, il, m] * qnm_i[i, il, m] ) qnarray[i, il] = qnormfac * ti.sqrt(qm_sum) if self.wlflag: idxcg_count = 0 for il in range(nqlist): l = qlist[il] wlsum = ti.f64(0.0) for m1 in range(2 * l + 1): for m2 in range( ti.max(0, l - m1), ti.min(2 * l + 1, 3 * l - m1 + 1) ): m = m1 + m2 - l qm1qm2_r = ( qnm_r[i, il, m1] * qnm_r[i, il, m2] - qnm_i[i, il, m1] * qnm_i[i, il, m2] ) qm1qm2_i = ( qnm_r[i, il, m1] * qnm_i[i, il, m2] + qnm_i[i, il, m1] * qnm_r[i, il, m2] ) wlsum += ( qm1qm2_r * qnm_r[i, il, m] + qm1qm2_i * qnm_i[i, il, m] ) * cglist[idxcg_count] idxcg_count += 1 qnarray[i, nqlist + il] = wlsum / ti.sqrt(2 * l + 1) if self.wlhatflag: idxcg_count = 0 for il in range(nqlist): l = qlist[il] wlsum = ti.f64(0.0) for m1 in range(2 * l + 1): for m2 in range( ti.max(0, l - m1), ti.min(2 * l + 1, 3 * l - m1 + 1) ): m = m1 + m2 - l qm1qm2_r = ( qnm_r[i, il, m1] * qnm_r[i, il, m2] - qnm_i[i, il, m1] * qnm_i[i, il, m2] ) qm1qm2_i = ( qnm_r[i, il, m1] * qnm_i[i, il, m2] + qnm_i[i, il, m1] * qnm_r[i, il, m2] ) wlsum += ( qm1qm2_r * qnm_r[i, il, m] + qm1qm2_i * qnm_i[i, il, m] ) * cglist[idxcg_count] idxcg_count += 1 if qnarray[i, il] >= 1e-6: qnormfac = ti.sqrt(4 * ti.math.pi / (2 * l + 1)) qnfac = qnormfac / qnarray[i, il] qnarray[i, nqlist + nqlist + il] = ( wlsum / ti.sqrt(2 * l + 1) * (qnfac * qnfac * qnfac) )
[docs] def compute(self): """Do the Steinhardt Bondorder calculation.""" qmax = self.qlist.max() self.qnm_r = np.zeros((self.pos.shape[0], self.nqlist, 2 * qmax + 1)) self.qnm_i = np.zeros_like(self.qnm_r) idxcg_count = self._get_idx(self.qlist) cglist = np.zeros(idxcg_count) if self.wlflag or self.wlhatflag: self._init_clebsch_gordan(cglist, self.qlist) self.qnarray = np.zeros((self.pos.shape[0], self.ncol)) if self.verlet_list is None or self.distance_list is None: if self.nnn > 0: kdt = NearestNeighbor(self.pos, self.box, self.boundary) self.distance_list, self.verlet_list = kdt.query_nearest_neighbors( self.nnn ) self.neighbor_number = np.ones(self.pos.shape[0], int) * self.nnn else: assert self.rc > 0.0, "one of rc and nnn at least is positive." neigh = Neighbor( self.pos, self.box, self.rc, self.boundary, max_neigh=self.max_neigh ) neigh.compute() self.distance_list, self.verlet_list, self.neighbor_number = ( neigh.verlet_list, neigh.distance_list, neigh.neighbor_number, ) else: if self.nnn > 0: assert ( self.neighbor_number.min() >= self.nnn ), "The minimum of neighbor_number should be larger than nnn." # self.neighbor_number = np.ones(self.pos.shape[0], int) * self.nnn self._compute( self.pos, self.box, self.verlet_list, self.distance_list, self.neighbor_number, self.qlist, self.qnm_r, self.qnm_i, self.qnarray, cglist, self.inverse_box, ) if self.old_N is not None: self.old_qnarray = self.qnarray.copy() self.qnarray = np.ascontiguousarray(self.qnarray[: self.old_N]) self.if_compute = True
@ti.kernel def _identifySolidLiquid( self, Q6index: int, Q6: ti.types.ndarray(), verlet_list: ti.types.ndarray(), distance_list: ti.types.ndarray(), qnm_r: ti.types.ndarray(), qnm_i: ti.types.ndarray(), neighbor_number: ti.types.ndarray(), threshold: float, n_bond: int, solidliquid: ti.types.ndarray(), ): for i in range(verlet_list.shape[0]): n_solid_bond = 0 for jj in range(neighbor_number[i]): j = verlet_list[i, jj] r = distance_list[i, jj] sij_sum = ti.f64(0.0) if r <= self.rc: for m in range(13): sij_sum += ( qnm_r[i, Q6index, m] * qnm_r[j, Q6index, m] + qnm_i[i, Q6index, m] * qnm_i[j, Q6index, m] ) sij_sum = sij_sum / Q6[i] / Q6[j] * 4 * ti.math.pi / 13 if sij_sum > threshold: n_solid_bond += 1 if n_solid_bond >= n_bond: solidliquid[i] = 1 for i in range(verlet_list.shape[0]): if solidliquid[i] == 1: n_solid = 0 for jj in range(neighbor_number[i]): j = verlet_list[i, jj] if solidliquid[j] == 1: n_solid = 1 break if n_solid == 0: solidliquid[i] = 0
[docs] def identifySolidLiquid(self, threshold=0.7, n_bond=7): """Identify the solid/liquid phase. Make sure you computed the 6 in qlist. Args: threshold (float, optional): threshold value to determine the solid bond. Defaults to 0.7. n_bond (int, optional): threshold to determine the solid atoms. Defaults to 7. """ assert 6 in self.qlist, "You must calculate Q6 bond order." if not self.if_compute: self.compute() Q6index = int(np.where(self.qlist == 6)[0][0]) if self.old_N is not None: Q6 = np.ascontiguousarray(self.old_qnarray[:, Q6index]) else: Q6 = np.ascontiguousarray(self.qnarray[:, Q6index]) self.solidliquid = np.zeros(self.pos.shape[0], int) self._identifySolidLiquid( Q6index, Q6, self.verlet_list, self.distance_list, self.qnm_r, self.qnm_i, self.neighbor_number, threshold, n_bond, self.solidliquid, ) if self.old_N is not None: self.solidliquid = np.ascontiguousarray(self.solidliquid[: self.old_N])
if __name__ == "__main__": from lattice_maker import LatticeMaker from time import time ti.init() start = time() FCC = LatticeMaker(3.615, "FCC", 10, 10, 10) FCC.compute() print(f"Build FCC time cost: {time()-start} s. Atom number: {FCC.N}.") start = time() BO = SteinhardtBondOrientation( FCC.pos, FCC.box, [1, 1, 1], None, None, None, 0.0, [4, 6, 8, 10], 12, wlflag=True, wlhatflag=False, ) BO.compute() print(f"BO time cost: {time()-start} s.") print(BO.qnarray[0]) start = time() BO.identifySolidLiquid() print(f"SolidLiquid time cost: {time()-start} s.") print(BO.solidliquid[:10])