Source code for geometricmd.geometry_pbc

# Load packages which are a part of GeometricMD
import numpy as np
from geometry import convert_atoms_to_vector, convert_vector_to_atoms

[docs]class Curve: """ The purpose of this object is to provide a Curve object that has similar behaviour to that described in [Sutton2013]_. Attributes: start_point (numpy.array): A NumPy array describing the first point in the curve. end_point (numpy.array): A NumPy array describing the last point in the curve. number_of_nodes (int): The total number of nodes that the curve is to consist of, including the start and end points. energy (float): The total Hamiltonian energy to be used in the simulation. tangent (numpy.array): The tangent of the straight line segment joining the start_point to the end_point, rescaled according to [Sutton2013]_. points (numpy.array): An NumPy array containing all the points of the curve. default_initial_state (numpy.array): A NumPy array consisting of flags that indicate which nodes are movable initially. movement (float): A variable which records the total movement of the curve as calculated in [Sutton2013]_. nodes_moved (numpy.array): A binary NumPy array indicating whether a node has been moved. Used to determine when all the nodes in the curve have been moved. node_movable (numpy.array): A binary NumPy array indicating whether a node is movable. number_of_distinct_nodes_moved (int): A counter recording the total number of nodes that have moved. configuration (dict): A dictionary containing the information from the configuration file. cells (list): A list of NumPy arrays describing the unit cell in each configuration. """
[docs] def __init__(self, start_point, end_point, number_of_nodes, energy): """The constructor for the Curve class. Note: This class is intended to be used by the SimulationServer module. Args: start_point (ase.atoms): An ASE atoms object describing the initial state. A calculator needs to be set on this object. end_point (ase.atoms): An ASE atoms object describing the final state. number_of_nodes (int): The number of nodes that the curve is to consist of, including the start and end points. energy (float): The total Hamiltonian energy to be used in the simulation. """ # Check if calculator attached to start_point, if not then raise an error. if start_point.get_calculator() == None: raise ValueError('Calculator not attached to start configuration.') # Pass the initialiser arguments directly into the class attributes self.start_point = np.asarray(convert_atoms_to_vector(start_point.get_positions()), dtype='float64') self.start_cell = start_point.get_cell() self.end_point = np.asarray(convert_atoms_to_vector(end_point.get_positions()), dtype='float64') self.end_cell = end_point.get_cell() self.number_of_nodes = int(number_of_nodes) self.energy = energy # Compute the tangent vector - the rescaled vector of the line joining the start and end points self.tangent = (1/(float(self.number_of_nodes)-1))*np.subtract(self.end_point, self.start_point) # Compute the initial curve, the straight line joining the start point to the end point self.points = np.asarray([self.start_point], dtype='float64') for i in xrange(0, int(self.number_of_nodes-1)): self.points = np.concatenate((self.points, [np.add(self.points[i], self.tangent)]), axis=0) np.concatenate((self.points, [self.end_point]), axis=0) # Compute the cell tangent vector - the rescaled vector of the line joining the start and end points self.cell_tangent = (1/(float(self.number_of_nodes)-1))*np.subtract(self.end_cell, self.start_cell) # Compute the cells, the straight line joining the start point to the end point self.cells = [self.start_cell] for i in xrange(0, int(self.number_of_nodes-1)): self.cells.append(np.add(self.cells[i], self.cell_tangent)) # Create and definite initial node_movable configuration. In this case even numbered nodes first. self.default_initial_state = np.zeros(self.number_of_nodes, dtype='int') for i in xrange(self.number_of_nodes-1): if i % 2 != 0: self.default_initial_state[i] = 2 # Create all of the flags in the curve to indicate that the nodes in default_initial_state are movable. self.movement = 0.0 self.nodes_moved = np.ones(self.number_of_nodes, dtype='int') self.node_movable = np.copy(self.default_initial_state) self.number_of_distinct_nodes_moved = 0 # Create the attribute to store the simulation configuration. self.configuration = {} self.configuration['molecule'] = start_point
[docs] def set_node_movable(self): """ Resets all of the flags in the curve to indicate that the current iteration of the Birkhoff algorithm is over. """ # Reset the total movement of the curve to zero self.movement = 0.0 # Reset the flag that indicates if a node has been moved self.nodes_moved = np.ones(self.number_of_nodes, dtype='int') # Reset the flags that indicate which nodes are movable back to the initial state self.node_movable = np.copy(self.default_initial_state) # Reset the count indicating the total number of distinct nodes that have been moved back to zero self.number_of_distinct_nodes_moved = 0
[docs] def set_node_position(self, node_number, new_position, new_cell): """ Update the position of the node at node_number to new_position. This processes the logic for releasing neighbouring nodes for further computation. Arguments: node_number (int): The node number of the node whose position is to be updated. new_position (numpy.array): The new position of the node. """ # Arithmetic to measure the total movement of a curve self.movement += float(1/float(self.number_of_nodes)) * \ np.linalg.norm(np.subtract(new_position, self.points[node_number])) # Update position of node with new position assumes new_position is float64 numpy array self.points[node_number] = new_position # Update new cell shape self.cells[node_number] = new_cell # Arithmetic for determining if an existing nodes neighbours have been moved self.node_movable[node_number-1] += 1 self.node_movable[node_number+1] += 1 self.node_movable[0] = 0 self.node_movable[-1] = 0 if node_number == 2: self.node_movable[1] += 1 self.node_movable[0] = 0 if node_number == self.number_of_nodes-3: self.node_movable[-2] += 1 self.node_movable[-1] = 0 # Arithmetic to keep track of how many nodes have been moved distinctly self.nodes_moved[node_number] = 0 self.number_of_distinct_nodes_moved = sum(self.nodes_moved)
[docs] def next(self): """ Determine next movable node, given existing information about previously distributed nodes. Used to ensure the curve object is Python iterable.. Returns: int: The node number of the next movable node. If no such node exists then it returns None. """ # This will get quicker with NumPy 2.0.0 - https://github.com/numpy/numpy/issues/2269 try: # Determine the node number of the next movable node in the next_movable_node array. This particular test # attempts to find a node that has not been previously moved. next_movable_node = np.where(np.multiply(self.node_movable, self.nodes_moved) > 1)[0][0] # If there is no node available that hasn't been previously moved then... if next_movable_node is None: # Simply find a node that is movable. This time we include previously moved nodes in our search next_movable_node = np.where(self.node_movable > 1)[0][0] # If we still couldn't find a node... if next_movable_node is None: # Raise a StopIteration raise StopIteration # Otherwise... else: # Mark the node as no longer movable to prevent it being re-issued. self.node_movable[next_movable_node] = 0 # Return the node number return next_movable_node except IndexError: # Raise a StopIteration raise StopIteration
[docs] def get_points(self): """ Accessor method for the points attribute. Returns: numpy.array: An array containing all of the points of the curve. """ return self.points
[docs] def all_nodes_moved(self): """ This method determines whether every node in the global curve has been tested for length reduction. Returns: bool: True if all of the nodes have been tested, False otherwise. """ if self.number_of_distinct_nodes_moved == 2: return True else: return False
[docs] def __iter__(self): """ This special method ensures a curve object is iterable. Returns: self """ return self