Module genice_core.dipole
Optimizes the orientations of directed paths to reduce the net dipole moment.
Expand source code
"""
Optimizes the orientations of directed paths to reduce the net dipole moment.
"""
from logging import getLogger, DEBUG
from typing import Union
import numpy as np
import networkx as nx
def vector_sum(
dg: nx.DiGraph, vertexPositions: np.ndarray, isPeriodicBoundary: bool = False
) -> np.ndarray:
"""Net polarization (actually a vector sum) of a digraph
Args:
dg (nx.DiGraph): The digraph.
vertexPositions (np.ndarray): Positions of the vertices.
isPeriodicBoundary (bool, optional): If true, the vertex positions must be in fractional coordinate. Defaults to False.
Returns:
np.ndarray: net polarization
"""
pol = np.zeros(3)
for i, j in dg.edges():
d = vertexPositions[j] - vertexPositions[i]
if isPeriodicBoundary:
d -= np.floor(d + 0.5)
pol += d
return pol
def optimize(
paths: list[list],
vertexPositions: np.ndarray,
dipoleOptimizationCycles: int = 2000,
isPeriodicBoundary: bool = False,
targetPol: Union[np.ndarray, None] = None,
) -> list[list]:
"""Minimize the net polarization by flipping several paths.
It is assumed that every vector has an identical dipole moment.
Args:
paths (list of list): List of directed paths. A path is a list of integer. A path with identical labels at first and last items are considered to be cyclic.
pos (nx.ndarray[*,3]): Positions of the nodes.
maxiter (int, optional): Number of random orientations for the paths. Defaults to 1000.
pbc (bool, optional): If `True`, the positions of the nodes must be in the fractional coordinate system.
target (np.ndarray, optional): Target value for the dipole-moment optimization.
Returns:
list of paths: Optimized paths.
"""
logger = getLogger()
if dipoleOptimizationCycles < 1:
return paths
if targetPol is None:
targetPol = np.zeros_like(vertexPositions[0])
# polarized chains and cycles. Small cycle of dipoles are eliminated.
polarizedEdges = []
dipoles = []
for i, path in enumerate(paths):
if isPeriodicBoundary:
# vectors between adjacent vertices.
relativeVector = vertexPositions[path[1:]] - vertexPositions[path[:-1]]
# PBC wrap
relativeVector -= np.floor(relativeVector + 0.5)
# total dipole along the chain (or a cycle)
chainPol = np.sum(relativeVector, axis=0)
# if it is large enough, i.e. if it is a spanning cycle,
if chainPol @ chainPol > 1e-6:
logger.debug(path)
dipoles.append(chainPol)
polarizedEdges.append(i)
else:
# dipole moment of a path; NOTE: No PBC.
if path[0] != path[-1]:
# If no PBC, a chain pol is simply an end-to-end pol.
chainPol = vertexPositions[path[-1]] - vertexPositions[path[0]]
dipoles.append(chainPol)
polarizedEdges.append(i)
dipoles = np.array(dipoles)
# logger.debug(dipoles)
optimalParities = np.ones(len(dipoles))
optimalPol = optimalParities @ dipoles - targetPol
logger.debug(f"init {optimalPol} dipole {targetPol}")
for loop in range(dipoleOptimizationCycles):
# random sequence of +1/-1
parities = np.random.randint(2, size=len(dipoles)) * 2 - 1
# Set directions to chains by parity.
pol = parities @ dipoles - targetPol
# If the new directions give better (smaller) net dipole moment,
if pol @ pol < optimalPol @ optimalPol:
# that is the optimal
optimalPol = pol
optimalParities = parities
logger.info(f"{loop} {optimalPol} dipole")
# if well-converged,
if optimalPol @ optimalPol < 1e-10:
logger.debug("Optimized.")
break
# invert some chains according to parity_optimal
for i, parity in zip(polarizedEdges, optimalParities):
if parity < 0:
# invert the chain
paths[i] = paths[i][::-1]
return paths
Functions
def optimize(paths: list[list], vertexPositions: numpy.ndarray, dipoleOptimizationCycles: int = 2000, isPeriodicBoundary: bool = False, targetPol: Optional[numpy.ndarray] = None) ‑> list[list]
-
Minimize the net polarization by flipping several paths.
It is assumed that every vector has an identical dipole moment.
Args
paths
:list
oflist
- List of directed paths. A path is a list of integer. A path with identical labels at first and last items are considered to be cyclic.
- pos (nx.ndarray[*,3]): Positions of the nodes.
maxiter
:int
, optional- Number of random orientations for the paths. Defaults to 1000.
pbc
:bool
, optional- If
True
, the positions of the nodes must be in the fractional coordinate system. target
:np.ndarray
, optional- Target value for the dipole-moment optimization.
Returns
list
ofpaths
- Optimized paths.
Expand source code
def optimize( paths: list[list], vertexPositions: np.ndarray, dipoleOptimizationCycles: int = 2000, isPeriodicBoundary: bool = False, targetPol: Union[np.ndarray, None] = None, ) -> list[list]: """Minimize the net polarization by flipping several paths. It is assumed that every vector has an identical dipole moment. Args: paths (list of list): List of directed paths. A path is a list of integer. A path with identical labels at first and last items are considered to be cyclic. pos (nx.ndarray[*,3]): Positions of the nodes. maxiter (int, optional): Number of random orientations for the paths. Defaults to 1000. pbc (bool, optional): If `True`, the positions of the nodes must be in the fractional coordinate system. target (np.ndarray, optional): Target value for the dipole-moment optimization. Returns: list of paths: Optimized paths. """ logger = getLogger() if dipoleOptimizationCycles < 1: return paths if targetPol is None: targetPol = np.zeros_like(vertexPositions[0]) # polarized chains and cycles. Small cycle of dipoles are eliminated. polarizedEdges = [] dipoles = [] for i, path in enumerate(paths): if isPeriodicBoundary: # vectors between adjacent vertices. relativeVector = vertexPositions[path[1:]] - vertexPositions[path[:-1]] # PBC wrap relativeVector -= np.floor(relativeVector + 0.5) # total dipole along the chain (or a cycle) chainPol = np.sum(relativeVector, axis=0) # if it is large enough, i.e. if it is a spanning cycle, if chainPol @ chainPol > 1e-6: logger.debug(path) dipoles.append(chainPol) polarizedEdges.append(i) else: # dipole moment of a path; NOTE: No PBC. if path[0] != path[-1]: # If no PBC, a chain pol is simply an end-to-end pol. chainPol = vertexPositions[path[-1]] - vertexPositions[path[0]] dipoles.append(chainPol) polarizedEdges.append(i) dipoles = np.array(dipoles) # logger.debug(dipoles) optimalParities = np.ones(len(dipoles)) optimalPol = optimalParities @ dipoles - targetPol logger.debug(f"init {optimalPol} dipole {targetPol}") for loop in range(dipoleOptimizationCycles): # random sequence of +1/-1 parities = np.random.randint(2, size=len(dipoles)) * 2 - 1 # Set directions to chains by parity. pol = parities @ dipoles - targetPol # If the new directions give better (smaller) net dipole moment, if pol @ pol < optimalPol @ optimalPol: # that is the optimal optimalPol = pol optimalParities = parities logger.info(f"{loop} {optimalPol} dipole") # if well-converged, if optimalPol @ optimalPol < 1e-10: logger.debug("Optimized.") break # invert some chains according to parity_optimal for i, parity in zip(polarizedEdges, optimalParities): if parity < 0: # invert the chain paths[i] = paths[i][::-1] return paths
def vector_sum(dg: networkx.classes.digraph.DiGraph, vertexPositions: numpy.ndarray, isPeriodicBoundary: bool = False) ‑> numpy.ndarray
-
Net polarization (actually a vector sum) of a digraph
Args
dg
:nx.DiGraph
- The digraph.
vertexPositions
:np.ndarray
- Positions of the vertices.
isPeriodicBoundary
:bool
, optional- If true, the vertex positions must be in fractional coordinate. Defaults to False.
Returns
np.ndarray
- net polarization
Expand source code
def vector_sum( dg: nx.DiGraph, vertexPositions: np.ndarray, isPeriodicBoundary: bool = False ) -> np.ndarray: """Net polarization (actually a vector sum) of a digraph Args: dg (nx.DiGraph): The digraph. vertexPositions (np.ndarray): Positions of the vertices. isPeriodicBoundary (bool, optional): If true, the vertex positions must be in fractional coordinate. Defaults to False. Returns: np.ndarray: net polarization """ pol = np.zeros(3) for i, j in dg.edges(): d = vertexPositions[j] - vertexPositions[i] if isPeriodicBoundary: d -= np.floor(d + 0.5) pol += d return pol