Source code for vermouth.processors.average_beads
# Copyright 2018 University of Groningen
#
# 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.
"""
Provides a processor that generates positions for nodes based on the weighted
average of the positions of the atoms they are constructed from.
"""
import numpy as np
from .processor import Processor
[docs]
def do_average_bead(molecule, ignore_missing_graphs=False, weight=None):
"""
Set the position of the particles to the mean of the underlying atoms.
This requires the atoms to have a 'graph' attributes. By default, a
:exc:`ValueError` is raised if any atom in the molecule is missing that
'graph' attribute. This behavior can be changed by setting the
'ignore_missing_graphs' argument to `True`, then the average positions are
computed, but the atoms without a 'graph' attribute are skipped.
The average is weighted using the 'mapping_weights' atom attribute. If the
'mapping_weights' attribute is set, it has to be a dictionary with the
atomname from the underlying graph as keys, and the weights as values.
Atoms without a weight set use a default weight of 1.
The average can also be weighted using an arbitrary node attribute by
giving the attribute name with the `weight` keyword argument. This can be
used to get the center of mass for instance; assuming the mass of the
underlying atoms is stored under the "mass" attribute, setting `weight` to
"mass" will place the bead at the center of mass. By default, `weight` is
set to `None` and the center of geometry is used.
The atoms in the underlying graph must have a position. If they do not,
they are ignored from the average.
Parameters
----------
molecule: vermouth.molecule.Molecule
The molecule to update. The attribute `position` of the particles
is updated on place. The nodes of the molecule must have an attribute
`graph` that contains the subgraph of the initial molecule.
ignore_missing_graphs: bool
If `True`, skip the atoms that do not have a `graph` attribute; else
fail if not all the atoms in the molecule have a `graph` attribute.
weight: collections.abc.Hashable
The name of the attribute used to weight the position of the node. The
attribute is read from the underlying atoms.
"""
# Make sure the molecule fullfill the requirements.
missing = []
for node in molecule.nodes.values():
if 'graph' not in node:
missing.append(node)
elif weight is not None:
have_all_weights = all(
weight in subnode for subnode in node['graph'].nodes.values()
)
if not have_all_weights:
raise KeyError('Not all underlying atoms have an attribute {}.'
.format(weight))
if missing and not ignore_missing_graphs:
raise ValueError('{} particles are missing the graph attribute'
.format(len(missing)))
for node in molecule.nodes.values():
if 'graph' in node:
positions = np.array([
subnode['position']
for subnode in node['graph'].nodes().values()
if subnode.get('position') is not None
])
weights = np.array([
node.get('mapping_weights', {}).get(subnode_key, 1) * subnode.get(weight, 1)
for subnode_key, subnode in node['graph'].nodes.items()
if subnode.get('position') is not None
])
try:
ndim = positions.shape[1]
except IndexError:
ndim = 3
if abs(sum(weights)) < 1e-7:
node['position'] = np.array([np.nan]*ndim, dtype=float)
else:
node['position'] = np.average(positions, axis=0, weights=weights)
return molecule
[docs]
class DoAverageBead(Processor):
def __init__(self, ignore_missing_graphs=False, weight=None):
super().__init__()
self.ignore_missing_graphs = ignore_missing_graphs
self.weight = weight
[docs]
def run_molecule(self, molecule):
if self.weight is None:
weight = molecule.force_field.variables.get('center_weight', None)
elif self.weight is False:
weight = None
else:
weight = self.weight
return do_average_bead(molecule, self.ignore_missing_graphs, weight=weight)