Source code for wepy.resampling.distances.distance
"""Modular component for defining distance metrics usable within many
different resamplers.
This module contains an abstract base class for Distance classes.
The suggested implementation method is to leave the 'distance' method
as is, and override the 'image' and 'image_distance' methods
instead. Because the default 'distance' method calls these
transparently. The resamplers will use the 'image' and
'image_distance' calls because this allows performance optimizations.
For example in WExplore the images for some walkers end up being
stored as the definitions of Voronoi regions, and if the whole walker
state was stored it would not only use much more space in memory but
require that common transformations be repeated every time a distance
is to be calculated to that image (which is many times). In REVO all
to all distances between walkers are computed which would also incur a
high cost.
So use the 'image' to do precomputations on raw walker states and use
the 'image_distance' to compute distances using only those images.
"""
# Standard Library
import logging
logger = logging.getLogger(__name__)
# Third Party Library
import numpy as np
from wepy.util.util import box_vectors_to_lengths_angles
[docs]
class Distance(object):
"""Abstract Base class for Distance classes."""
def __init__(self):
"""Constructor for Distance class."""
pass
[docs]
def image(self, state):
"""Compute the 'image' of a walker state which should be some
transformation of the walker state that is more
convenient. E.g. for precomputation of expensive operations or
for saving as resampler state.
The abstract implementation is naive and just returns the
state itself, thus it is the identity function.
Parameters
----------
state : object implementing WalkerState
The state which will be transformed to an image
Returns
-------
image : object implementing WalkerState
The same state that was given as an argument.
"""
return state
[docs]
def image_distance(self, image_a, image_b):
"""Compute the distance between two images of walker states.
Parameters
----------
image_a : object produced by Distance.image
image_b : object produced by Distance.image
Returns
-------
distance : float
The distance between the two images
Raises
------
NotImplementedError : always because this is abstract
"""
raise NotImplementedError
[docs]
def distance(self, state_a, state_b):
"""Compute the distance between two states.
Parameters
----------
state_a : object implementing WalkerState
state_b : object implementing WalkerState
Returns
-------
distance : float
The distance between the two walker states
"""
return self.image_distance(self.image(state_a), self.image(state_b))
[docs]
class XYEuclideanDistance(Distance):
"""2 dimensional euclidean distance between points.
States have the attributes 'x' and 'y'.
"""
[docs]
def image(self, state):
return np.array([state["x"], state["y"]])
[docs]
def image_distance(self, image_a, image_b):
return np.sqrt((image_a[0] - image_b[0]) ** 2 + (image_a[1] - image_b[1]) ** 2)
[docs]
class AtomPairDistance(Distance):
"""Constructs a vector of atomic distances for each state.
Distance is the root mean squared distance between the vectors.
"""
def __init__(self, pair_list, periodic=True):
"""Construct a distance metric.
Parameters
----------
pair_list : arraylike of tuples
The indices of the atom pairs between which to compute
distances.
"""
self.pair_list = pair_list
self.periodic = periodic
[docs]
def _adjust_disp_vector(self, disp, box_lengths):
edited = True
while edited:
edited = False
for i in range(3):
if disp[i] > box_lengths[i]/2:
disp[i] -= box_lengths[i]
edited = True
elif disp[i] < -box_lengths[i]/2:
disp[i] += box_lengths[i]
edited = True
return disp
[docs]
def image(self, state):
if self.periodic:
# get the box lengths from the vectors
box_lengths, box_angles = box_vectors_to_lengths_angles(state["box_vectors"])
dist_list = np.zeros((len(self.pair_list)))
for i,p in enumerate(self.pair_list):
disp_vector = state["positions"][p[0]] - state["positions"][p[1]]
if self.periodic:
dist_list[i] = np.sqrt(np.sum(np.square(self._adjust_disp_vector(disp_vector,box_lengths))))
else:
dist_list[i] = np.sqrt(np.sum(np.square(disp_vector)))
return dist_list
[docs]
def image_distance(self, image_a, image_b):
return np.sqrt(np.mean(np.square(image_a - image_b)))