From be289dc7df19e8c719a5374f3d97f8407f4cb968 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Fri, 21 Jun 2019 14:42:15 +0100 Subject: [PATCH 1/5] add pypi deployment to travis --- .gitignore | 2 ++ .travis.yml | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8657568..9680353 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ *.swp ~* *~ +build/ +dist/ .project *.egg* .DS_Store diff --git a/.travis.yml b/.travis.yml index 3ece177..3b05b9f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ matrix: - python: 3.6 env: TEST_TYPE="version" - python: 3.6 - env: TEST_TYPE="pytest" TEST_AIIDA_BACKEND="django" + env: TEST_TYPE="pytest" TEST_AIIDA_BACKEND="django" PYPI_DEPLOY=true - python: 2.7 env: TEST_TYPE="pytest" TEST_AIIDA_BACKEND="django" - python: 3.6 @@ -81,3 +81,14 @@ script: # after_success: # - coveralls + +deploy: + - provider: pypi + distributions: "sdist bdist_wheel" + user: cjsewell + password: + secure: Jrqfy5jCez0BKUi5dBi+pXxRBlmF24T53ZL6FumhKOTXgsjk8hp723GGJ4iJwdmnDBPQX029cJ0ebwZrNjTCI538TNDQaT+f2B7pUp0HG5qreWhbYQaMEU20wPvvyIbiI3Wei87PvouV2OHLelWj9/Q8Gy7GHQr1+66g/H83E27WgG56Qer3HcGCcUHZ03iByq640eF1ipqMs276pI9/tzeiCPqnOo5tlB2Nb+5McKtMoBnB8zn8930FxkYY3BijZWTN2fiEMx9ezDQDutGiZ6HU9c8ZJhw4A0pAETVIbdZAWcHyqnAknruX3q4eb8JKzKCEMBSiH4YS/0Sl5In4jTkrhTCiWLr5ViNDor9P+XNW6pgNS1BSdePj+gQ+E1v/+DMgnHJ4pcch2aILYAvF7R8txxEc+5xVtalGK0KjNXs+2eK/57Fqpozy2SnqfqlV1DeS+fo+/LAvyl0p7Gxt/mJCQgpHZOrcEsfW0PC2WsVmn0E5n7HAivTd3tqeY8z97KtQHiKGNa0SMzUvO7Gt9AxXtXG1sdQBJgdCO9iYpsk37WIlCsmzJM+w9cxbU69/QkKLPvr+PSxy6A3FeWKKonJ6uSlALl6rljOqutDlAKjIrsfSlykm9UPBS6yy2Ist2YJF6YRmeiaEA+3MGArZONaEmxOiau06BmtzFSMf7CM= + on: + branch: master + tags: true + condition: $PYPI_DEPLOY = true From 5bfce0cedb409ab1788c4900445f15c61903e6a2 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Fri, 21 Jun 2019 14:53:02 +0100 Subject: [PATCH 2/5] add pypi installation instructions to readme --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 945b45f..dcf40a3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![Build Status](https://travis-ci.org/abelcarreras/aiida-lammps.svg?branch=master)](https://travis-ci.org/abelcarreras/aiida-lammps) +[![PyPI](https://img.shields.io/pypi/v/aiida-lammps.svg)](https://pypi.python.org/pypi/aiida-lammps/) # AiiDA LAMMPS plugin @@ -14,6 +15,7 @@ Note: `lammps.combinate` requires `aiida-phonopy` (https://github.com/abelcarrer plugin to work, DynaPhoPy can be found in: https://github.com/abelcarreras/aiida-phonopy - [AiiDA LAMMPS plugin](#AiiDA-LAMMPS-plugin) + - [Installation](#Installation) - [Built-in Potential Support](#Built-in-Potential-Support) - [Examples](#Examples) - [Code Setup](#Code-Setup) @@ -23,6 +25,14 @@ plugin to work, DynaPhoPy can be found in: https://github.com/abelcarreras/aiida - [Optimisation Calculation](#Optimisation-Calculation) - [MD Calculation](#MD-Calculation) +## Installation + +To install from pypi: + +```shell +pip install aiida-lammps +``` + ## Built-in Potential Support - EAM From f4b62a11ea6b98952746f9c3514410ef25a18a48 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Fri, 21 Jun 2019 18:30:48 +0100 Subject: [PATCH 3/5] version bump --- aiida_lammps/__init__.py | 2 +- setup.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aiida_lammps/__init__.py b/aiida_lammps/__init__.py index 342dbf2..7c5a130 100644 --- a/aiida_lammps/__init__.py +++ b/aiida_lammps/__init__.py @@ -1 +1 @@ -__version__ = "0.4.0b3" +__version__ = "0.4.1b3" diff --git a/setup.json b/setup.json index b092f0e..ccb403f 100644 --- a/setup.json +++ b/setup.json @@ -1,8 +1,8 @@ { "name": "aiida-lammps", - "version": "0.4.0b3", + "version": "0.4.1b3", "description": "AiiDA plugin for LAMMPS", - "url": "https://github.com/abelcarreras/aiida_extensions", + "url": "https://github.com/abelcarreras/aiida-lammps", "author": "Abel Carreras", "author_email": "abelcarreras83@gmail.com", "license": "MIT license", From c958668e0caef74b0f7499e13c0d0f37ebd66a20 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Sat, 22 Jun 2019 12:59:06 +0100 Subject: [PATCH 4/5] swap dateutils dependency for python-dateutil both include the same modules, but python-dateutil is better maintained --- setup.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.json b/setup.json index ccb403f..346c416 100644 --- a/setup.json +++ b/setup.json @@ -10,7 +10,7 @@ "aiida-core==1.0.0b3", "numpy", "packaging", - "dateutils", + "python-dateutil", "jsonschema", "six", "ase>=3.12.0,<4.0.0" From e0d98fa59e8d6e5f74e2612786dfefc51dcb6546 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Sat, 22 Jun 2019 21:27:23 +0100 Subject: [PATCH 5/5] improve `generate_structure` Using ` np.linalg.qr` we can remove a lot of extraneous code, regarding the transformation of the cell and atomic positions, to LAMMPS compatible format. We also store the transformation matrix, so it will be possible to transform back, during the parsing stage. --- aiida_lammps/calculations/lammps/__init__.py | 15 +- aiida_lammps/common/generate_structure.py | 264 +++--------------- aiida_lammps/tests/test_generate_structure.py | 4 +- .../test_generate_Fe_.txt | 2 + .../test_generate_pyrite_.txt | 2 + 5 files changed, 61 insertions(+), 226 deletions(-) diff --git a/aiida_lammps/calculations/lammps/__init__.py b/aiida_lammps/calculations/lammps/__init__.py index 65a5c7e..d829b06 100644 --- a/aiida_lammps/calculations/lammps/__init__.py +++ b/aiida_lammps/calculations/lammps/__init__.py @@ -110,7 +110,7 @@ class BaseLammpsCalculation(CalcJob): _DEFAULT_OUTPUT_INFO_FILE_NAME = "system_info.dump" _DEFAULT_OUTPUT_RESTART_FILE_NAME = 'lammps.restart' - _retrieve_list = ['log.lammps'] + _retrieve_list = [] _retrieve_temporary_list = [] _cmdline_params = ['-in', _INPUT_FILE_NAME] _stdout_name = None @@ -123,6 +123,8 @@ def define(cls, spec): help='lammps potential') spec.input('parameters', valid_type=Dict, help='the parameters', required=False) + spec.input('metadata.options.cell_transform_filename', + valid_type=six.string_types, default="cell_transform.npy") spec.input('metadata.options.output_filename', valid_type=six.string_types, default=cls._DEFAULT_OUTPUT_FILE_NAME) spec.input('metadata.options.trajectory_name', @@ -199,8 +201,11 @@ def prepare_for_submission(self, tempfolder): potential_txt = self.inputs.potential.get_potential_file() # Setup structure - structure_txt = generate_lammps_structure(self.inputs.structure, - self.inputs.potential.atom_style) + structure_txt, struct_transform = generate_lammps_structure( + self.inputs.structure, self.inputs.potential.atom_style) + + with open(tempfolder.get_abs_path(self.options.cell_transform_filename), 'w+b') as handle: + np.save(handle, struct_transform) if "parameters" in self.inputs: parameters = self.inputs.parameters @@ -255,7 +260,9 @@ def prepare_for_submission(self, tempfolder): calcinfo = CalcInfo() calcinfo.uuid = self.uuid - calcinfo.retrieve_list = self._retrieve_list + calcinfo.retrieve_list = self._retrieve_list + [ + self.options.output_filename, + self.options.cell_transform_filename] calcinfo.retrieve_temporary_list = self._retrieve_temporary_list calcinfo.codes_info = [codeinfo] diff --git a/aiida_lammps/common/generate_structure.py b/aiida_lammps/common/generate_structure.py index 8d3fbdd..a093663 100644 --- a/aiida_lammps/common/generate_structure.py +++ b/aiida_lammps/common/generate_structure.py @@ -1,84 +1,35 @@ """ creation of the structure file content - -The code here is largely adapted from https://github.com/andeplane/cif2cell-lammps -ESPInterfaces.LAMMPSFile """ -from math import cos, sin, atan2 -import ase +# import ase import numpy as np -def get_vector(value): - return [float(v) for v in value] - - -def get_matrix(value): - return [[float(v) for v in vec] for vec in value] - - -def mvmult3(mat, vec): - """ matrix-vector multiplication """ - w = [0., 0., 0.] - for i in range(3): - t = 0 - for j in range(3): - t = t + mat[j][i] * vec[j] - w[i] = t - return w - - -def cartesian_to_frac(lattice, ccoords): - """convert cartesian coordinate to fractional +def transform_cell(cell): + """transform the cell to an orientation, compatible with LAMMPS - Parameters - ---------- - lattice: list - 3x3 array of lattice vectors - ccoord: list - Nx3 cartesian coordinates + LAMMPS requires the simulation cell to be in the format of a + lower triangular matrix (right-handed basis). + Therefore the cell and positions may require rotation and inversion. + See https://lammps.sandia.gov/doc/Howto_triclinic.html - Returns - ------- - list: - Nx3 array of fractional coordinate + :param cell: (3x3) lattice + :returns: (new_cell, transform) """ - det3 = np.linalg.det - latt_tr = np.transpose(lattice) - det_latt_tr = np.linalg.det(latt_tr) - - fcoords = [] - for ccoord in ccoords: - a = (det3([[ccoord[0], latt_tr[0][1], latt_tr[0][2]], - [ccoord[1], latt_tr[1][1], latt_tr[1][2]], - [ccoord[2], latt_tr[2][1], latt_tr[2][2]]])) / det_latt_tr - b = (det3([[latt_tr[0][0], ccoord[0], latt_tr[0][2]], - [latt_tr[1][0], ccoord[1], latt_tr[1][2]], - [latt_tr[2][0], ccoord[2], latt_tr[2][2]]])) / det_latt_tr - c = (det3([[latt_tr[0][0], latt_tr[0][1], ccoord[0]], - [latt_tr[1][0], latt_tr[1][1], ccoord[1]], - [latt_tr[2][0], latt_tr[2][1], ccoord[2]]])) / det_latt_tr - - fcoords.append([a, b, c]) - - return fcoords - - -def is_not_zero(value): - return not np.isclose(value, 0) - - -def round_by(value, round_dp): - if round_dp is None: - return value - return round(value, round_dp) + cell = np.array(cell) + transform, upper_tri = np.linalg.qr(cell.T, mode="complete") + new_cell = np.transpose(upper_tri) + # LAMMPS also requires positive values on the diagonal of the, + # so invert cell if necessary + inversion = np.eye(3) + for i in range(3): + if new_cell[i][i] < 0.0: + inversion[i][i] = -1.0 + new_cell = np.dot(inversion, new_cell.T).T + transform = np.dot(transform, inversion.T).T -class AtomSite(object): - def __init__(self, kind_name, cartesian, fractional=None): - self.kind_name = kind_name - self.cartesian = cartesian - self.fractional = fractional + return new_cell, transform def generate_lammps_structure(structure, @@ -100,17 +51,22 @@ def generate_lammps_structure(structure, docstring : str docstring to put at top of file + Returns + ------- + str: content + the structure file content + numpy.array: transform + the transformation matrix applied to the structure cell and coordinates + """ if atom_style not in ['atomic', 'charge']: raise ValueError("atom_style must be in ['atomic', 'charge']") if charge_dict is None: charge_dict = {} - atom_sites = [AtomSite(site.kind_name, site.position) - for site in structure.sites] # mapping of atom kind_name to id number kind_name_id_map = {} - for site in atom_sites: + for site in structure.sites: if site.kind_name not in kind_name_id_map: kind_name_id_map[site.kind_name] = len(kind_name_id_map) + 1 # mapping of atom kind_name to mass @@ -118,80 +74,22 @@ def generate_lammps_structure(structure, filestring = "" filestring += "# {}\n\n".format(docstring) - filestring += "{0} atoms\n".format(len(atom_sites)) + filestring += "{0} atoms\n".format(len(structure.sites)) filestring += "{0} atom types\n\n".format(len(kind_name_id_map)) - lattice = get_matrix(structure.cell) - - # As per https://lammps.sandia.gov/doc/Howto_triclinic.html, - # if the lattice does not conform to a regular parallelpiped - # then it must first be rotated - - if is_not_zero(lattice[0][1]) or is_not_zero(lattice[0][2]) or is_not_zero(lattice[1][2]): - rotated_cell = True - for site in atom_sites: - site.fractional = cartesian_to_frac(lattice, [site.cartesian])[0] - # creating the cell from its lengths and angles, - # generally ensures that it is in a compatible orientation - atoms = ase.Atoms(cell=structure.cell_lengths + structure.cell_angles) - lattice = get_matrix(atoms.cell) - else: - rotated_cell = False - - if is_not_zero(lattice[0][1]): - theta = atan2(-lattice[0][1], lattice[0][0]) - rot_matrix = get_matrix([ - [cos(theta), sin(theta), 0], - [-sin(theta), cos(theta), 0], - [0, 0, 1] - ]) - lattice[0] = get_vector(mvmult3(rot_matrix, lattice[0])) - lattice[1] = get_vector(mvmult3(rot_matrix, lattice[1])) - lattice[2] = get_vector(mvmult3(rot_matrix, lattice[2])) - - if is_not_zero(lattice[0][2]): - theta = atan2(-lattice[0][2], lattice[0][0]) - rot_matrix = get_matrix([ - [cos(theta), sin(theta), 0], - [0, 1, 0], - [-sin(theta), cos(theta), 0] - ]) - lattice[0] = get_vector(mvmult3(rot_matrix, lattice[0])) - lattice[1] = get_vector(mvmult3(rot_matrix, lattice[1])) - lattice[2] = get_vector(mvmult3(rot_matrix, lattice[2])) + atoms = structure.get_ase() + cell, coord_transform = transform_cell(atoms.cell) + positions = np.transpose(np.dot(coord_transform, np.transpose(atoms.positions))) - if is_not_zero(lattice[1][2]): - theta = atan2(-lattice[1][2], lattice[1][1]) - rot_matrix = get_matrix([ - [1, 0, 0], - [0, cos(theta), sin(theta)], - [0, -sin(theta), cos(theta)] - ]) - lattice[0] = get_vector(mvmult3(rot_matrix, lattice[0])) - lattice[1] = get_vector(mvmult3(rot_matrix, lattice[1])) - lattice[2] = get_vector(mvmult3(rot_matrix, lattice[2])) + if round_dp: + cell = np.round(cell, round_dp) + 0. + positions = np.round(positions, round_dp) + 0. - if is_not_zero(lattice[0][1]) or is_not_zero(lattice[0][2]) or is_not_zero(lattice[1][2]) or lattice[0][0] < 1e-9 or lattice[1][1] < 1e-9 or lattice[2][2] < 1e-9: - raise ValueError( - "Error in triclinic box: {}\n" - "Vectors should follow these rules: " - "https://lammps.sandia.gov/doc/Howto_triclinic.html".format(lattice)) - - a = round_by(lattice[0][0], round_dp) - b = round_by(lattice[1][1], round_dp) - c = round_by(lattice[2][2], round_dp) - - filestring += "0.0 {0:20.10f} xlo xhi\n".format(a) - filestring += "0.0 {0:20.10f} ylo yhi\n".format(b) - filestring += "0.0 {0:20.10f} zlo zhi\n".format(c) - - xy = round_by(lattice[1][0], round_dp) - xz = round_by(lattice[2][0], round_dp) - yz = round_by(lattice[2][1], round_dp) - - if is_not_zero(xy) or is_not_zero(xz) or is_not_zero(yz): - filestring += "{0:20.10f} {1:20.10f} {2:20.10f} xy xz yz\n\n".format( - xy, xz, yz) + filestring += "0.0 {0:20.10f} xlo xhi\n".format(cell[0][0]) + filestring += "0.0 {0:20.10f} ylo yhi\n".format(cell[1][1]) + filestring += "0.0 {0:20.10f} zlo zhi\n".format(cell[2][2]) + filestring += "{0:20.10f} {1:20.10f} {2:20.10f} xy xz yz\n\n".format( + cell[1][0], cell[2][0], cell[2][1]) filestring += 'Masses\n\n' for kind_name in sorted(list(kind_name_id_map.keys())): @@ -201,12 +99,7 @@ def generate_lammps_structure(structure, filestring += "Atoms\n\n" - for site_index, site in enumerate(atom_sites): - if rotated_cell: - pos = get_vector(mvmult3(lattice, site.fractional)) - else: - pos = site.cartesian - pos = [round_by(v, round_dp) for v in pos] + for site_index, (pos, site) in enumerate(zip(positions, structure.sites)): kind_id = kind_name_id_map[site.kind_name] @@ -218,75 +111,6 @@ def generate_lammps_structure(structure, filestring += "{0} {1} {2} {3:20.10f} {4:20.10f} {5:20.10f}\n".format( site_index + 1, kind_id, charge, pos[0], pos[1], pos[2]) else: - raise ValueError('atom_style') - - return filestring - - -def old_generate_lammps_structure(structure, atom_style): - """ this is the deprecated method, used before 0.3.0b3, - stored here for prosperity. - - This method can create erroneous structures for triclinic cells - """ - import numpy as np - - types = [site.kind_name for site in structure.sites] - - type_index_unique = np.unique(types, return_index=True)[1] - count_index_unique = np.diff(np.append(type_index_unique, [len(types)])) - - atom_index = [] - for i, index in enumerate(count_index_unique): - atom_index += [i for j in range(index)] - - masses = [site.mass for site in structure.kinds] - positions = [site.position for site in structure.sites] - - number_of_atoms = len(positions) - - lammps_data_file = 'Generated using dynaphopy\n\n' - lammps_data_file += '{0} atoms\n\n'.format(number_of_atoms) - lammps_data_file += '{0} atom types\n\n'.format(len(masses)) - - cell = np.array(structure.cell) - - a = np.linalg.norm(cell[0]) - b = np.linalg.norm(cell[1]) - c = np.linalg.norm(cell[2]) - - alpha = np.arccos(np.dot(cell[1], cell[2]) / (c * b)) - gamma = np.arccos(np.dot(cell[1], cell[0]) / (a * b)) - beta = np.arccos(np.dot(cell[2], cell[0]) / (a * c)) - - xhi = a - xy = b * np.cos(gamma) - xz = c * np.cos(beta) - yhi = np.sqrt(pow(b, 2) - pow(xy, 2)) - yz = (b * c * np.cos(alpha) - xy * xz) / yhi - zhi = np.sqrt(pow(c, 2) - pow(xz, 2) - pow(yz, 2)) - - xhi = xhi + max(0, 0, xy, xz, xy + xz) - yhi = yhi + max(0, 0, yz) - - lammps_data_file += '\n{0:20.10f} {1:20.10f} xlo xhi\n'.format(0, xhi) - lammps_data_file += '{0:20.10f} {1:20.10f} ylo yhi\n'.format(0, yhi) - lammps_data_file += '{0:20.10f} {1:20.10f} zlo zhi\n'.format(0, zhi) - lammps_data_file += '{0:20.10f} {1:20.10f} {2:20.10f} xy xz yz\n\n'.format( - xy, xz, yz) - - lammps_data_file += 'Masses\n\n' - - for i, mass in enumerate(masses): - lammps_data_file += '{0} {1:20.10f} \n'.format(i + 1, mass) - - lammps_data_file += '\nAtoms\n\n' - for i, row in enumerate(positions): - if atom_style == 'charge': - lammps_data_file += '{0} {1} 0.0 {2:20.10f} {3:20.10f} {4:20.10f}\n'.format( - i + 1, atom_index[i] + 1, row[0], row[1], row[2]) - else: - lammps_data_file += '{0} {1} {2:20.10f} {3:20.10f} {4:20.10f}\n'.format( - i + 1, atom_index[i] + 1, row[0], row[1], row[2]) + raise ValueError('atom_style unknown: {}'.format(atom_style)) - return lammps_data_file + return filestring, coord_transform diff --git a/aiida_lammps/tests/test_generate_structure.py b/aiida_lammps/tests/test_generate_structure.py index 5288c70..772f029 100644 --- a/aiida_lammps/tests/test_generate_structure.py +++ b/aiida_lammps/tests/test_generate_structure.py @@ -11,5 +11,5 @@ ]) def test_generate(db_test_app, get_structure_data, structure, file_regression): structure = get_structure_data(structure) - file_regression.check(six.ensure_text( - generate_lammps_structure(structure, round_dp=8))) + text, transform = generate_lammps_structure(structure, round_dp=8) + file_regression.check(six.ensure_text(text)) diff --git a/aiida_lammps/tests/test_generate_structure/test_generate_Fe_.txt b/aiida_lammps/tests/test_generate_structure/test_generate_Fe_.txt index e749747..a97698d 100644 --- a/aiida_lammps/tests/test_generate_structure/test_generate_Fe_.txt +++ b/aiida_lammps/tests/test_generate_structure/test_generate_Fe_.txt @@ -6,6 +6,8 @@ 0.0 2.8481160000 xlo xhi 0.0 2.8481160000 ylo yhi 0.0 2.8481160000 zlo zhi + 0.0000000000 0.0000000000 0.0000000000 xy xz yz + Masses 1 55.8450000000 diff --git a/aiida_lammps/tests/test_generate_structure/test_generate_pyrite_.txt b/aiida_lammps/tests/test_generate_structure/test_generate_pyrite_.txt index e15b880..4f89112 100644 --- a/aiida_lammps/tests/test_generate_structure/test_generate_pyrite_.txt +++ b/aiida_lammps/tests/test_generate_structure/test_generate_pyrite_.txt @@ -6,6 +6,8 @@ 0.0 5.3800000000 xlo xhi 0.0 5.3800000000 ylo yhi 0.0 5.3800000000 zlo zhi + 0.0000000000 0.0000000000 0.0000000000 xy xz yz + Masses 1 55.8450000000