diff --git a/CMakeLists.txt b/CMakeLists.txt
index 612387e4..56cc1e1f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-CMAKE_MINIMUM_REQUIRED(VERSION 3.12)
+CMAKE_MINIMUM_REQUIRED(VERSION 3.20.1)
project(Pylada
VERSION 1.0.1
DESCRIPTION "A pythonic computation material science platform"
@@ -49,21 +49,14 @@ endif()
set(OLD_CMAKE_FIND_FRAMEWORK "${CMAKE_FIND_FRAMEWORK}")
set(CMAKE_FIND_FRAMEWORK LAST)
-find_package(Python3 COMPONENTS Development Interpreter)
+find_package(Python COMPONENTS Development.Module Interpreter REQUIRED)
set(CMAKE_FIND_FRAMEWORK "${OLD_CMAKE_FIND_FRAMEWORK}")
-if(NOT Python3_FOUND)
+if(Python2_VERSION_MAJOR EQUAL 2)
set(CMAKE_FIND_FRAMEWORK LAST)
- find_package(Python2 COMPONENTS Development Interpreter)
set(CMAKE_FIND_FRAMEWORK "${OLD_CMAKE_FIND_FRAMEWORK}")
-endif()
-if(NOT Python3_FOUND AND NOT Python2_FOUND)
- message(FATAL_ERROR "Could find neither python3 nor python2")
-endif()
-
-if(Python3_FOUND)
- set(PYVER PY3)
-else()
set(PYVER PY2)
+else()
+ set(PYVER PY3)
endif()
if(NOT SKBUILD)
@@ -85,7 +78,6 @@ if(NOT SKBUILD)
list(APPEND CMAKE_MODULE_PATH "${SKBUILD_LOCATION}/resources/cmake")
endif()
-find_package(PythonExtensions REQUIRED)
find_package(Cython REQUIRED)
find_package(NumPy REQUIRED)
find_package(Eigen3 NO_MODULE)
diff --git a/cmake_modules/eigen/CMakeLists.txt b/cmake_modules/eigen/CMakeLists.txt
index 391edf08..df6bcfcf 100644
--- a/cmake_modules/eigen/CMakeLists.txt
+++ b/cmake_modules/eigen/CMakeLists.txt
@@ -1,5 +1,5 @@
project(external CXX)
-cmake_minimum_required(VERSION 3.12)
+cmake_minimum_required(VERSION 3.20.1)
include("ExternalProject")
if(NOT EIGEN_TAR_ARCHIVE)
diff --git a/src/pylada/CMakeLists.txt b/src/pylada/CMakeLists.txt
index 7bccf299..a72e0423 100644
--- a/src/pylada/CMakeLists.txt
+++ b/src/pylada/CMakeLists.txt
@@ -8,7 +8,6 @@ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/_version.py"
)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/_version.py" DESTINATION ${PY_ROOT_DIR})
-include_directories("${PYTHON_INCLUDE_DIR}")
add_subdirectory(crystal)
add_subdirectory(decorations)
add_subdirectory(ewald)
diff --git a/src/pylada/crystal/CMakeLists.txt b/src/pylada/crystal/CMakeLists.txt
index e704e5f5..20282d88 100644
--- a/src/pylada/crystal/CMakeLists.txt
+++ b/src/pylada/crystal/CMakeLists.txt
@@ -30,18 +30,21 @@ set(cython_sources
foreach(filename ${cython_sources})
get_filename_component(name "${filename}" NAME_WE)
add_cython_target(cython_${name} ${filename} CXX ${PYVER})
- add_library(${name} MODULE ${cython_${name}})
- python_extension_module(${name})
- target_link_libraries(${name} Eigen3::Eigen)
- target_include_directories(${name} PRIVATE "${PY_HEADER_DIR}" "${NumPy_INCLUDE_DIRS}")
+
+ python_add_library(${name} MODULE WITH_SOABI ${cython_${name}})
+
+ target_link_libraries(${name} PRIVATE Eigen3::Eigen)
+ target_include_directories(${name} PRIVATE "${PY_HEADER_DIR}" "${NumPy_INCLUDE_DIRS}" "${Python3_INCLUDE_DIRS}")
install(TARGETS ${name} LIBRARY DESTINATION ${PY_ROOT_DIR}/crystal)
endforeach()
add_cython_target(cython_cutilities cutilities.pyx CXX ${PYVER})
-add_library(cutilities MODULE ${cython_cutilities} smith_normal_form.cc gruber.cc noopt.cc)
-python_extension_module(cutilities)
-target_include_directories(cutilities PRIVATE "${PY_HEADER_DIR}" "${NumPy_INCLUDE_DIRS}")
-target_link_libraries(cutilities Eigen3::Eigen)
+
+python_add_library(cutilities MODULE WITH_SOABI ${cython_cutilities} smith_normal_form.cc gruber.cc noopt.cc)
+
+
+target_include_directories(cutilities PRIVATE "${PY_HEADER_DIR}" "${NumPy_INCLUDE_DIRS}" "${Python3_INCLUDE_DIRS}")
+target_link_libraries(cutilities PRIVATE Eigen3::Eigen)
install(TARGETS cutilities LIBRARY DESTINATION ${PY_ROOT_DIR}/crystal)
add_subdirectory(defects)
diff --git a/src/pylada/crystal/_coordination_shells.pyx b/src/pylada/crystal/_coordination_shells.pyx
index 19f84dd9..cff1dff3 100644
--- a/src/pylada/crystal/_coordination_shells.pyx
+++ b/src/pylada/crystal/_coordination_shells.pyx
@@ -65,7 +65,7 @@ def coordination_shells(structure, int nshells, center, tolerance=1e-12, natoms=
from . import gruber, into_voronoi
cdef int N = len(structure)
- cell = gruber(structure.cell, 3e0*tolerance)
+ cell = gruber(structure.cell, tolerance = 3e0*tolerance)
invcell = inv(cell)
cdef double volume = structure.volume
cdef int maxatoms = __natoms(natoms, nshells)
@@ -154,7 +154,7 @@ def __neighbors(structure, int nmax, center, tolerance=1e-12, natoms=0):
from . import gruber, into_voronoi
cdef int N = len(structure)
- cell = gruber(structure.cell, 3e0*tolerance)
+ cell = gruber(structure.cell, tolerance = 3e0*tolerance)
invcell = inv(cell)
cdef double volume = structure.volume
cdef int maxatoms = max([natoms, nmax + 2])
diff --git a/src/pylada/crystal/_primitive.pyx b/src/pylada/crystal/_primitive.pyx
index 92f094c1..832a0de2 100644
--- a/src/pylada/crystal/_primitive.pyx
+++ b/src/pylada/crystal/_primitive.pyx
@@ -20,10 +20,10 @@
# .
###############################
-cdef __translations(structure, double tolerance):
+def __translations(structure, double tolerance):
""" Looks for internal translations """
from numpy.linalg import inv
- from numpy import all, abs, allclose
+ from numpy import all, abs, allclose, mod
from . import gruber, into_cell, into_voronoi
cell = gruber(structure.cell)
@@ -32,32 +32,36 @@ cdef __translations(structure, double tolerance):
front_type = structure[0].type
center = structure[0].pos
translations = []
+
for site in structure:
if front_type != site.type:
continue
- translation = into_voronoi(site.pos - center, cell, invcell)
+ translation = into_voronoi(site.pos - center + tolerance, cell, invcell) - tolerance
+
if all(abs(translation) < tolerance):
continue
for mapping in structure:
- pos = into_cell(mapping.pos + translation, cell, invcell)
+ pos = into_cell(mapping.pos + translation + tolerance, cell, invcell) - tolerance
for mappee in structure:
- if mapping.type == mappee.type and allclose(mappee.pos, pos, tolerance):
+ if mapping.type == mappee.type and allclose(into_cell(mappee.pos + tolerance, cell, invcell)-tolerance, pos, atol=tolerance):
break
else:
break
else:
- translations.append(into_voronoi(translation, cell, invcell))
+ yield into_voronoi(translation, cell, invcell)
- return translations
+ yield cell[:, 0]
+ yield cell[:, 1]
+ yield cell[:, 2]
-def primitive(structure, double tolerance=1e-8):
+def primitive(structure, double tolerance=1e-8, bint recursive=False):
""" Tries to compute the primitive cell of the input structure """
# The tolerance is the absolute tolerance on the translations
from numpy.linalg import inv, det
- from numpy import all, abs, array, dot, allclose, round
+ from numpy import all, abs, array, dot, allclose, round, mod
from . import gruber, into_cell, into_voronoi, into_cell
from .. import error, logger
@@ -68,32 +72,29 @@ def primitive(structure, double tolerance=1e-8):
cell = gruber(result.cell)
invcell = inv(cell)
for atom in result:
- atom.pos = into_cell(atom.pos, cell, invcell)
+ atom.pos = into_cell(atom.pos + tolerance, cell, invcell) - tolerance
- translations = __translations(result, tolerance)
- if len(translations) == 0:
+ for i,t in enumerate(__translations(result, tolerance)):
+ if i > 2:
+ break
+ else:
logger.debug("Found no inner translations: structure is primitive")
for a, b in zip(result, structure):
a.pos[:] = b.pos[:]
return result
-
- # adds original translations.
- translations.append(cell[:, 0])
- translations.append(cell[:, 1])
- translations.append(cell[:, 2])
-
+
# Looks for cell with smallest volume
new_cell = result.cell.copy()
volume = abs(det(new_cell))
- for i, first in enumerate(translations):
- for j, second in enumerate(translations):
+ for i, first in enumerate(__translations(result, tolerance)):
+ for j, second in enumerate(__translations(result, tolerance)):
if i == j:
continue
- for k, third in enumerate(translations):
+ for k, third in enumerate(__translations(result, tolerance)):
if i == k or j == k:
continue
trial = array([first, second, third]).T
- if abs(det(trial)) < abs(volume)/len(structure)*(1 - 3*tolerance): # The volume of the cell cannot be smaller than the specific volume
+ if abs(det(trial)) < abs(result.volume)/len(structure)*(1 - 3*tolerance): # The volume of the cell cannot be smaller than the specific volume
continue
if abs(det(trial)) > volume*(1 - 3.0 * tolerance):
continue
@@ -106,30 +107,39 @@ def primitive(structure, double tolerance=1e-8):
logger.error(msg)
raise error.RuntimeError(msg)
integer_cell = dot(inv(trial), cell)
- if allclose(integer_cell, round(integer_cell + 1e-7), tolerance): # This needs to be a supercell within the specified tolerance
+ if allclose(integer_cell, round(integer_cell + 1e-7), atol = tolerance): # This needs to be a supercell within the specified tolerance
new_cell = trial
volume = abs(det(trial))
-
+ if recursive:
+ break
+ else:
+ continue
+ break
+ else:
+ continue
+ break
+
# Found the new cell with smallest volume (e.g. primivite)
if abs(structure.volume - volume) < 1e-12: # It actually did not change
msg = "Found translation but no primitive cell."
logger.error(msg)
raise error.RuntimeError(msg)
-
+
# now creates new lattice.
result.clear()
logger.debug("Found potential cell {!r}".format(new_cell))
result.cell = gruber(new_cell)
invcell = inv(result.cell)
for site in structure:
- pos = into_cell(site.pos, result.cell, invcell)
+ pos = result.cell.dot(mod(invcell.dot(site.pos) + tolerance, 1) - tolerance)
+ # pos = into_cell(site.pos + tolerance, result.cell, invcell)-tolerance
for unique in result:
- if site.type == unique.type and allclose(unique.pos, pos, abs(structure.volume / result.volume) * tolerance): # The difference between pos and unique pos is that of site.pos and the image of unique.pos
+ if site.type == unique.type and allclose(unique.pos, pos, atol = abs(structure.volume / result.volume) * tolerance): # The difference between pos and unique pos is that of site.pos and the image of unique.pos
break
else:
result.append(site.copy())
result[-1].pos = pos
-
+
if len(structure) % len(result) != 0:
msg = "Nb of atoms in output not multiple of input."
logger.error(msg)
@@ -141,7 +151,11 @@ def primitive(structure, double tolerance=1e-8):
raise error.RuntimeError(msg)
logger.debug("Primitive structure found with %i/%i atoms" % (len(result), len(structure)))
- return result;
+
+ if recursive:
+ return primitive(result, tolerance=tolerance, recursive=True)
+ else:
+ return result
def is_primitive(structure, double tolerance = 1e-12):
""" True if the lattice is primitive
@@ -165,4 +179,8 @@ def is_primitive(structure, double tolerance = 1e-12):
for atom in result:
atom.pos = into_cell(atom.pos, cell, invcell)
- return len(__translations(result, tolerance)) == 0
+ for i,t in enumerate(__translations(result, tolerance)):
+ if i > 2:
+ return False
+ else:
+ return True
diff --git a/src/pylada/crystal/_space_group.pyx b/src/pylada/crystal/_space_group.pyx
index 91a340f0..6101f3e4 100644
--- a/src/pylada/crystal/_space_group.pyx
+++ b/src/pylada/crystal/_space_group.pyx
@@ -21,6 +21,7 @@
###############################
import numpy as np
cimport numpy as np
+np.import_array()
cpdef __gvectors(double[:, ::1] cell, double tolerance):
""" Computes all gvectors in prolate defined by cell """
diff --git a/src/pylada/crystal/cutilities.pyx b/src/pylada/crystal/cutilities.pyx
index 920cb988..261b6c49 100644
--- a/src/pylada/crystal/cutilities.pyx
+++ b/src/pylada/crystal/cutilities.pyx
@@ -1,5 +1,6 @@
import numpy as np
cimport numpy as np
+np.import_array()
cdef extern from "pylada/crystal/types.h" namespace "pylada::types":
ctypedef int t_int
diff --git a/src/pylada/crystal/defects/CMakeLists.txt b/src/pylada/crystal/defects/CMakeLists.txt
index 0c54f549..c5ddea6a 100644
--- a/src/pylada/crystal/defects/CMakeLists.txt
+++ b/src/pylada/crystal/defects/CMakeLists.txt
@@ -24,11 +24,12 @@
###############################
add_cython_target(defects_cython _defects.pyx CXX ${PYVER})
-add_library(_defects MODULE ${defects_cython} third_order.cc)
-python_extension_module(_defects)
-target_link_libraries(_defects Eigen3::Eigen)
+
+python_add_library(_defects MODULE WITH_SOABI ${defects_cython} third_order.cc)
+
+target_link_libraries(_defects PRIVATE Eigen3::Eigen)
target_include_directories(
_defects PRIVATE
- "${PY_HEADER_DIR}" "${NumPy_INCLUDE_DIRS}" "${PYTHON_INCLUDE_DIR}"
+ "${PY_HEADER_DIR}" "${NumPy_INCLUDE_DIRS}" "${Python3_INCLUDE_DIR}"
)
install(TARGETS _defects LIBRARY DESTINATION ${PY_ROOT_DIR}/crystal/defects)
diff --git a/src/pylada/crystal/read.py b/src/pylada/crystal/read.py
index d3066128..0796ed58 100644
--- a/src/pylada/crystal/read.py
+++ b/src/pylada/crystal/read.py
@@ -89,12 +89,23 @@ def poscar(path="POSCAR", types=None):
for i in line:
if not re.match(r"[A-Z][a-z]?", i):
is_vasp_5 = False
- break
+ break
if is_vasp_5:
text_types = deepcopy(line)
- if types is not None and not set(text_types).issubset(set(types)):
- raise error.ValueError("Unknown species in poscar: {0} not in {1}."
- .format(text_types, types))
+
+ if types is not None:
+ # If partial H (e.g. H.75) are specified they will replace H
+ # in the order they were specified
+ for s in types:
+ if s[0] == "H" and "." in s:
+ for i,c in enumerate(text_types):
+ if c == "H":
+ text_types[i] = s
+ break
+
+ if not set(text_types).issubset(set(types)):
+ raise error.ValueError("Unknown species in poscar: {0} not in {1}."
+ .format(text_types, types))
types = text_types
line = poscar.readline().split()
if types is None:
diff --git a/src/pylada/decorations/CMakeLists.txt b/src/pylada/decorations/CMakeLists.txt
index 06d3124a..2dee0bf6 100644
--- a/src/pylada/decorations/CMakeLists.txt
+++ b/src/pylada/decorations/CMakeLists.txt
@@ -21,10 +21,11 @@
###############################
add_cython_target(decorations_cython _decorations.pyx CXX ${PYVER}})
-add_library(_decorations MODULE ${decorations_cython})
-python_extension_module(_decorations)
+
+python_add_library(_decorations MODULE WITH_SOABI ${decorations_cython})
+
target_include_directories(
_decorations PRIVATE
- "${PY_HEADER_DIR}" "${NumPy_INCLUDE_DIRS}" "${PYTHON_INCLUDE_DIR}"
+ "${PY_HEADER_DIR}" "${NumPy_INCLUDE_DIRS}" "${Python3_INCLUDE_DIR}"
)
install(TARGETS _decorations LIBRARY DESTINATION ${PY_ROOT_DIR}/decorations)
diff --git a/src/pylada/ewald/CMakeLists.txt b/src/pylada/ewald/CMakeLists.txt
index f07a93e5..831588f9 100644
--- a/src/pylada/ewald/CMakeLists.txt
+++ b/src/pylada/ewald/CMakeLists.txt
@@ -20,8 +20,8 @@
# .
###############################
add_cython_target(ewald_cython ewald.pyx CXX ${PYVER})
-add_library(ewald MODULE
- ${ewald_cython} ep_com.f90 erfc.cc ewaldf.f90 ewald.cc)
-python_extension_module(ewald)
-target_include_directories(ewald PRIVATE "${PY_HEADER_DIR}")
+
+python_add_library(ewald MODULE WITH_SOABI ${ewald_cython} ep_com.f90 erfc.cc ewaldf.f90 ewald.cc)
+
+target_include_directories(ewald PRIVATE "${PY_HEADER_DIR}" "${Python3_INCLUDE_DIR}")
install(TARGETS ewald LIBRARY DESTINATION ${PY_ROOT_DIR}/ewald)
diff --git a/src/pylada/ipython/launch/__init__.py b/src/pylada/ipython/launch/__init__.py
index 24d8049f..d77ac9a9 100644
--- a/src/pylada/ipython/launch/__init__.py
+++ b/src/pylada/ipython/launch/__init__.py
@@ -167,7 +167,6 @@ def mppalloc(job):
""" Returns number of processes for this job. """
natom = len(job.structure) # number of atoms.
# Round down to a multiple of ppn
-#vladan nnode = max(1, natom / event.ppn)
nnode = max(1, natom // event.ppn)
nproc = nnode * event.ppn
return nproc
diff --git a/src/pylada/vasp/extract/base.py b/src/pylada/vasp/extract/base.py
index ead78ca5..4e497c75 100644
--- a/src/pylada/vasp/extract/base.py
+++ b/src/pylada/vasp/extract/base.py
@@ -269,12 +269,12 @@ def structure(self):
""" Greps structure and total energy from OUTCAR. """
if self.nsw == 0 or self.ibrion == -1:
return self.initial_structure
-
+
try:
result = self._contcar_structure
except:
result = self._grep_structure
-
+
# tries to find adequate name for structure.
try:
name = self.system
@@ -329,6 +329,7 @@ def structure(self):
else:
for force, atom in zip(forces, result):
atom.force = force
+
return result
@property
@@ -447,7 +448,7 @@ def _contcar_structure(self):
""" Greps structure from CONTCAR. """
from ...crystal import read
from quantities import eV
-
+
result = read.poscar(self.__contcar__(), self.species)
result.energy = float(self.total_energy.rescale(eV)) if self.is_dft else 0e0
return result
@@ -470,8 +471,21 @@ def ions_per_specie(self):
@property
@make_cached
def species(self):
+ import re
""" Greps species from OUTCAR. """
- return [ u.group(1) for u in self._search_OUTCAR(r"""VRHFIN\s*=\s*(\S+)\s*:""") ]
+ result = []
+ for u in self._search_OUTCAR(r"""VRHFIN\s*=\s*(.*)$"""):
+ atom = u[1].split(":")[0].strip()
+ match = re.search(r"Z\s*=\s*(\S+)", u[1])
+ if match is None:
+ result.append(atom)
+ else:
+ result.append(atom + ("%3.2f"%(eval(match[1]))).strip(" 0"))
+
+ # return [ u.group(1) for u in self._search_OUTCAR(r"""VRHFIN\s*=\s*(\S+)\s*:""") ]
+ # return [ u.group(1) for u in self._search_OUTCAR(r"""TITEL\s*=\s*\S*\s*(\S+)""") ]
+
+ return result
@property
@make_cached
diff --git a/src/pylada/vasp/functional.py b/src/pylada/vasp/functional.py
index 9fdeef70..13c1bddb 100644
--- a/src/pylada/vasp/functional.py
+++ b/src/pylada/vasp/functional.py
@@ -304,7 +304,7 @@ def __init__(self, copy=None, species=None, kpoints=None, **kwargs):
.. _CONTCAR: http://cms.mpi.univie.ac.at/vasp/guide/node60.html
"""
- self.isym = ChoiceKeyword(values=list(range(-1, 3)))
+ self.isym = ChoiceKeyword(values=list(range(-1, 4)))
""" Symmetry scheme.
.. seealso:: ISYM_
@@ -1017,8 +1017,9 @@ def bringup(self, structure, outdir, **kwargs):
# creates POTCAR file
logger.debug("vasp/functional bringup: files.POTCAR: %s " % files.POTCAR)
with open(files.POTCAR, 'w') as potcar:
- self.write_potcar(potcar, structure)
-
+ for s in specieset(structure):
+ outLines = self.species[s].read_potcar()
+ potcar.writelines(outLines)
# Add is running file marker.
local_path(outdir).join('.pylada_is_running').ensure(file=True)
self._copy_additional_files(outdir)
@@ -1114,14 +1115,7 @@ def write_kpoints(self, file, structure, kpoints=None):
outLine = "{0[0]} {0[1]} {0[2]} {1}\n".format(
kpoint, 1 if len(kpoint) == 3 else kpoint[3])
file.write(outLine)
-
- def write_potcar(self, file, structure):
- """ Writes the potcar file """
- from ..crystal import specieset
- for s in specieset(structure):
- outLines = self.species[s].read_potcar()
- file.writelines(outLines)
-
+
def __repr__(self, defaults=True, name=None):
""" Returns representation of this instance """
from ..tools.uirepr import uirepr
diff --git a/src/pylada/vasp/keywords.py b/src/pylada/vasp/keywords.py
index 1d596769..8998b45f 100644
--- a/src/pylada/vasp/keywords.py
+++ b/src/pylada/vasp/keywords.py
@@ -356,7 +356,10 @@ def value(self, value):
if value is None:
self._value = None
return None
- from pylada import is_vasp_4
+ try:
+ from pylada import is_vasp_4
+ except:
+ is_vasp_4 = False
if not hasattr(value, 'lower'):
raise TypeError("ALGO cannot be set with {0}.".format(value))
lower = value.lower().rstrip().lstrip()
@@ -871,8 +874,7 @@ def output_map(self, **kwargs):
from ..error import ValueError
from ..crystal import write, read, specieset
from . import files
- from pylada import is_vasp_4
-
+
istruc = self._value
if istruc is None:
istruc = 0
@@ -886,7 +888,7 @@ def output_map(self, **kwargs):
has_restart = vasp.restart.success
if has_restart:
structure = vasp.restart.structure
-
+
# determines which CONTCAR is the latest, if any exist.
if istruc in [-1, 1]:
last_contcar = latest_file(join(outdir, files.CONTCAR))
@@ -896,6 +898,7 @@ def output_map(self, **kwargs):
# specifically, the order of the atoms of a given specie are the same).
if last_contcar is not None:
other = read.poscar(path=last_contcar, types=specieset(structure))
+
if len(other) != len(structure):
raise ValueError('CONTCAR and input structure differ in size.')
for type in specieset(other):
@@ -908,6 +911,9 @@ def output_map(self, **kwargs):
if a.type != type:
continue
a.pos = b.pos
+ if len(getattr(b, 'freeze', '')) != 0:
+ a.freeze = b.freeze
+
structure.cell = other.cell
structure.scale = other.scale
print("setting cell and scale!")
@@ -920,7 +926,8 @@ def output_map(self, **kwargs):
raise ValueError('Structure scale is zero')
if structure.volume < 1e-8:
raise ValueError('Structure volume is zero')
- write.poscar(structure, join(outdir, 'POSCAR'), vasp5=not is_vasp_4)
+
+ write.poscar(structure, join(outdir, 'POSCAR'))
return None