Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' of github.com:materialsproject/pymatgen

  • Loading branch information...
commit 5ada31c2630afecb14f1ca1812618fae578a6b4a 2 parents 45a634c + 658663e
Dan Gunter dangunter authored
1  pymatgen/core/periodic_table.py
View
@@ -21,6 +21,7 @@
from pymatgen.util.string_utils import formula_double_format
from pymatgen.serializers.json_coders import MSONable
+
def _load__pt_data():
"""Loads element data from json file"""
module_dir = os.path.dirname(os.path.abspath(__file__))
223 pymatgen/core/structure.py
View
@@ -56,7 +56,8 @@ def __init__(self, atoms_n_occu, coords, properties=None):
"""
if isinstance(atoms_n_occu, dict):
- self._species = {smart_element_or_specie(k): v for k, v in atoms_n_occu.items()}
+ self._species = {smart_element_or_specie(k): v \
+ for k, v in atoms_n_occu.items()}
totaloccu = sum(self._species.values())
if totaloccu > 1:
raise ValueError("Species occupancies sum to more than 1!")
@@ -68,7 +69,7 @@ def __init__(self, atoms_n_occu, coords, properties=None):
self._properties = properties if properties else {}
for k in self._properties.keys():
if k not in Site.supported_properties:
- raise ValueError("{} is not a supported Site property".format(k))
+ raise ValueError("{} is not a supported property".format(k))
@property
def properties(self):
@@ -111,7 +112,8 @@ def species_string(self):
return str(self._species.keys()[0])
else:
sorted_species = sorted(self._species.keys())
- return ', '.join(["%s:%.3f" % (str(sp), self._species[sp]) for sp in sorted_species])
+ return ', '.join(["%s:%.3f" % (str(sp), self._species[sp]) \
+ for sp in sorted_species])
@property
def species_and_occu(self):
@@ -187,7 +189,8 @@ def __eq__(self, other):
"""
if other == None:
return False
- return self._species == other._species and np.allclose(self._coords, other._coords)
+ return self._species == other._species and \
+ np.allclose(self._coords, other._coords)
def __ne__(self, other):
return not self.__eq__(other)
@@ -238,7 +241,8 @@ def avg_electroneg(sps):
return 0
def __str__(self):
- return "%s %s" % (self._coords, ','.join(["%s %.4f" % (str(atom), occu) for atom, occu in self._species.items()]))
+ return "%s %s" % (self._coords, ','.join(["%s %.4f" % (str(atom), occu)\
+ for atom, occu in self._species.items()]))
@property
def to_dict(self):
@@ -250,7 +254,9 @@ def to_dict(self):
d = spec.to_dict
d['occu'] = occu
species_list.append(d)
- d = {'name': self.species_string, 'species': species_list, 'occu': occu, 'xyz':[float(c) for c in self._coords], 'properties': self._properties}
+ d = {'name': self.species_string, 'species': species_list,
+ 'occu': occu, 'xyz':[float(c) for c in self._coords],
+ 'properties': self._properties}
d['module'] = self.__class__.__module__
d['class'] = self.__class__.__name__
return d
@@ -262,7 +268,8 @@ def from_dict(d):
"""
atoms_n_occu = {}
for sp_occu in d['species']:
- sp = Specie.from_dict(sp_occu) if 'oxidation_state' in sp_occu else Element(sp_occu['element'])
+ sp = Specie.from_dict(sp_occu) if 'oxidation_state' in sp_occu \
+ else Element(sp_occu['element'])
atoms_n_occu[sp] = sp_occu['occu']
props = d.get('properties', None)
return Site(atoms_n_occu, d['xyz'], properties=props)
@@ -299,11 +306,12 @@ def __init__(self, atoms_n_occu, coords, lattice, to_unit_cell=False,
{'magmom':5}. Defaults to None.
"""
self._lattice = lattice
- self._fcoords = self._lattice.get_fractional_coords(coords) if coords_are_cartesian else coords
+ self._fcoords = self._lattice.get_fractional_coords(coords) \
+ if coords_are_cartesian else coords
if to_unit_cell:
- for i in range(len(self._fcoords)):
- self._fcoords[i] = self._fcoords[i] - math.floor(self._fcoords[i])
+ for i, fcoords in enumerate(self._fcoords):
+ self._fcoords[i] = fcoords - math.floor(fcoords)
c_coords = self._lattice.get_cartesian_coords(self._fcoords)
Site.__init__(self, atoms_n_occu, c_coords, properties)
@@ -358,11 +366,14 @@ def is_periodic_image(self, other, tolerance=1e-8):
if self.lattice != other.lattice:
return False
frac_diff = abs(np.array(self._fcoords) - np.array(other._fcoords)) % 1
- frac_diff = [abs(a) < tolerance or abs(a) > 1 - tolerance for a in frac_diff]
+ frac_diff = [abs(a) < tolerance or abs(a) > 1 - tolerance \
+ for a in frac_diff]
return all(frac_diff)
def __eq__(self, other):
- return self._species == other._species and self._lattice == other._lattice and np.allclose(self._coords, other._coords)
+ return self._species == other._species and \
+ self._lattice == other._lattice and \
+ np.allclose(self._coords, other._coords)
def __ne__(self, other):
return not self.__eq__(other)
@@ -403,7 +414,8 @@ def distance_and_image_old(self, other, jimage=None):
if jimage == None:
#Old algorithm
jimage = -np.array(np.around(other._fcoords - self._fcoords), int)
- return np.linalg.norm(self.lattice.get_cartesian_coords(jimage + other._fcoords - self._fcoords)), jimage
+ return np.linalg.norm(self.lattice.get_cartesian_coords(jimage + \
+ other._fcoords - self._fcoords)), jimage
def distance_and_image_from_frac_coords(self, fcoords, jimage=None):
"""
@@ -441,7 +453,8 @@ def distance_and_image_from_frac_coords(self, fcoords, jimage=None):
coord2 = fcoords + adj2
#Generate set of images required for testing.
- test_set = [[-1, 0] if coord1[i] < coord2[i] else [0, 1] for i in range(3)]
+ test_set = [[-1, 0] if coord1[i] < coord2[i] else [0, 1] \
+ for i in range(3)]
images = [image for image in itertools.product(*test_set)]
#Create vectorized cartesian coords tiling for computing distances.
@@ -457,7 +470,8 @@ def distance_and_image_from_frac_coords(self, fcoords, jimage=None):
ind = dist.index(mindist)
return mindist, adj2 - adj1 + images[ind]
- return np.linalg.norm(self.lattice.get_cartesian_coords(jimage + fcoords - self._fcoords)), jimage
+ return np.linalg.norm(self.lattice.get_cartesian_coords(jimage + \
+ fcoords - self._fcoords)), jimage
def distance_and_image(self, other, jimage=None):
"""
@@ -493,8 +507,9 @@ def distance(self, other, jimage=None):
other site to get distance from.
jimage:
specific periodic image in terms of lattice translations,
- e.g., [1,0,0] implies to take periodic image that is one a-lattice vector away.
- if jimage == None, the image that is nearest to the site is found.
+ e.g., [1,0,0] implies to take periodic image that is one
+ a-lattice vector away. If jimage == None, the image that is
+ nearest to the site is found.
Returns:
distance:
@@ -545,7 +560,8 @@ def from_dict(d, lattice=None):
"""
atoms_n_occu = {}
for sp_occu in d['species']:
- sp = Specie.from_dict(sp_occu) if 'oxidation_state' in sp_occu else Element(sp_occu['element'])
+ sp = Specie.from_dict(sp_occu) if 'oxidation_state' in sp_occu \
+ else Element(sp_occu['element'])
atoms_n_occu[sp] = sp_occu['occu']
props = d.get('properties', None)
lattice = lattice if lattice else Lattice.from_dict(d['lattice'])
@@ -742,7 +758,8 @@ def get_dihedral(self, i, j, k, l):
v3 = self.sites[i].coords - self.sites[j].coords
v23 = np.cross(v2, v3)
v12 = np.cross(v1, v2)
- return math.atan2(np.linalg.norm(v2) * np.dot(v1, v23), np.dot(v12, v23)) * 180 / math.pi
+ return math.atan2(np.linalg.norm(v2) * np.dot(v1, v23),
+ np.dot(v12, v23)) * 180 / math.pi
class Structure(SiteCollection, MSONable):
@@ -785,7 +802,9 @@ def __init__(self, lattice, atomicspecies, coords, validate_proximity=False,
Defaults to None for no properties.
"""
if len(atomicspecies) != len(coords):
- raise StructureError("The list of atomic species must be of the same length as the list of fractional coordinates.")
+ raise StructureError(("The list of atomic species must be of the",
+ "same length as the list of fractional",
+ " coordinates."))
if isinstance(lattice, Lattice):
self._lattice = lattice
@@ -797,12 +816,16 @@ def __init__(self, lattice, atomicspecies, coords, validate_proximity=False,
prop = None
if site_properties:
prop = {k:v[i] for k, v in site_properties.items()}
- self._sites.append(PeriodicSite(atomicspecies[i], coords[i], self._lattice, to_unit_cell, coords_are_cartesian, properties=prop))
+ self._sites.append(PeriodicSite(atomicspecies[i], coords[i],
+ self._lattice, to_unit_cell,
+ coords_are_cartesian,
+ properties=prop))
if validate_proximity:
for (s1, s2) in itertools.combinations(self._sites, 2):
if s1.distance(s2) < SiteCollection.DISTANCE_TOLERANCE:
- raise StructureError("Structure contains sites that are less than 0.01 Angstrom apart!")
+ raise StructureError(("Structure contains sites that are ",
+ "less than 0.01 Angstrom apart!"))
self._sites = tuple(self._sites)
@@ -920,7 +943,8 @@ def get_sites_in_sphere(self, pt, r):
axis_ranges.append(range(rangemin, rangemax + 1))
neighbors = []
n = len(self._sites)
- site_fcoords = np.array([site.to_unit_cell.frac_coords for site in self._sites])
+ site_fcoords = np.array([site.to_unit_cell.frac_coords \
+ for site in self._sites])
pts = np.array([pt] * n)
for image in itertools.product(*axis_ranges):
shift = np.array([image] * n)
@@ -930,7 +954,10 @@ def get_sites_in_sphere(self, pt, r):
withindists = (dists <= r)
for i in range(n):
if withindists[i]:
- neighbors.append((PeriodicSite(self._sites[i].species_and_occu, fcoords[i], self._lattice, properties=self._sites[i].properties), dists[i]))
+ nnsite = PeriodicSite(self._sites[i].species_and_occu,
+ fcoords[i], self._lattice,
+ properties=self._sites[i].properties)
+ neighbors.append((nnsite, dists[i]))
return neighbors
def get_neighbors(self, site, r):
@@ -948,7 +975,8 @@ def get_neighbors(self, site, r):
[(site, dist) ...] since most of the time, subsequent processing
requires the distance.
"""
- return [(s, dist) for (s, dist) in self.get_sites_in_sphere(site.coords, r) if site != s]
+ nn = self.get_sites_in_sphere(site.coords, r)
+ return [(s, dist) for (s, dist) in nn if site != s]
def get_all_neighbors(self, r, include_index=False):
"""
@@ -996,7 +1024,8 @@ def get_all_neighbors(self, r, include_index=False):
inymin = int(math.floor(pcoords[1] - nmax[1]))
inzmax = int(math.floor(pcoords[2] + nmax[2]))
inzmin = int(math.floor(pcoords[2] - nmax[2]))
- site_nminmax.append(((inxmin, inxmax), (inymin, inymax), (inzmin, inzmax)))
+ site_nminmax.append(((inxmin, inxmax), (inymin, inymax),
+ (inzmin, inzmax)))
nxmin = min([i[0][0] for i in site_nminmax])
nxmax = max([i[0][1] for i in site_nminmax])
@@ -1009,7 +1038,9 @@ def get_all_neighbors(self, r, include_index=False):
site_coords = np.array(self.cart_coords)
n = len(self._sites)
- for image in itertools.product(range(nxmin, nxmax + 1), range(nymin, nymax + 1), range(nzmin, nzmax + 1)):
+ for image in itertools.product(range(nxmin, nxmax + 1),
+ range(nymin, nymax + 1),
+ range(nzmin, nzmax + 1)):
for (j, site) in enumerate(unit_cell_sites):
fcoords = site.frac_coords + np.array(image)
coords = self.lattice.get_cartesian_coords(fcoords)
@@ -1018,10 +1049,13 @@ def get_all_neighbors(self, r, include_index=False):
dists = np.sqrt(dists.sum(axis=1))
withindists = (dists <= r) * (dists > 1e-8)
for i in range(n):
- if include_index & withindists[i]:
- neighbors[i].append((PeriodicSite(site.species_and_occu, fcoords, site.lattice, properties=site.properties), dists[i], j))
- elif withindists[i]:
- neighbors[i].append((PeriodicSite(site.species_and_occu, fcoords, site.lattice, properties=site.properties), dists[i]))
+ if withindists[i]:
+ nnsite = PeriodicSite(site.species_and_occu, fcoords,
+ site.lattice,
+ properties=site.properties)
+ item = (nnsite, dists[i], j) if include_index \
+ else (nnsite, dists[i])
+ neighbors[i].append(item)
return neighbors
def get_neighbors_in_shell(self, origin, r, dr):
@@ -1051,7 +1085,9 @@ def get_sorted_structure(self):
Sites are sorted by the electronegativity of the species.
"""
sortedsites = sorted(self.sites)
- return Structure(self._lattice, [site.species_and_occu for site in sortedsites], [site.frac_coords for site in sortedsites])
+ return Structure(self._lattice,
+ [site.species_and_occu for site in sortedsites],
+ [site.frac_coords for site in sortedsites])
def interpolate(self, end_structure, nimages=10):
'''
@@ -1069,22 +1105,27 @@ def interpolate(self, end_structure, nimages=10):
'''
#Check length of structures
if len(self) != len(end_structure):
- raise ValueError("You are interpolating structures with different lengths!")
+ raise ValueError("Structures have different lengths!")
#Check that both structures have the same lattice
- if not ((abs(self.lattice.matrix - end_structure.lattice.matrix) < 0.01)).all():
- raise ValueError("You are interpolating structures with different lattices!")
+ if not np.allclose(self.lattice.matrix, end_structure.lattice.matrix):
+ raise ValueError("Structures with different lattices!")
#Check that both structures have the same species
for i in range(0, len(self)):
if self[i].species_and_occu != end_structure[i].species_and_occu:
- raise ValueError("You are interpolating different structures!\nStructure 1:\n" + str(self) + "\nStructure 2\n" + str(end_structure))
+ raise ValueError("Different species!\nStructure 1:\n" + \
+ str(self) + "\nStructure 2\n" + \
+ str(end_structure))
start_coords = np.array(self.frac_coords)
end_coords = np.array(end_structure.frac_coords)
vec = end_coords - start_coords #+ jimage
- intStructs = [Structure(self.lattice, [site.species_and_occu for site in self._sites], start_coords + float(x) / float(nimages) * vec) for x in range(0, nimages + 1)]
+ intStructs = [Structure(self.lattice,
+ [site.species_and_occu for site in self._sites],
+ start_coords + float(x) / float(nimages) * vec)\
+ for x in range(0, nimages + 1)]
return intStructs
def __repr__(self):
@@ -1099,11 +1140,14 @@ def __str__(self):
outs = ["Structure Summary ({s})".format(s=str(self.composition))]
outs.append("Reduced Formula: " + str(self.composition.reduced_formula))
to_s = lambda x : "%0.6f" % x
- outs.append('abc : ' + " ".join([to_s(i).rjust(10) for i in self.lattice.abc]))
- outs.append('angles: ' + " ".join([to_s(i).rjust(10) for i in self.lattice.angles]))
+ outs.append('abc : ' + " ".join([to_s(i).rjust(10) \
+ for i in self.lattice.abc]))
+ outs.append('angles: ' + " ".join([to_s(i).rjust(10) \
+ for i in self.lattice.angles]))
outs.append("Sites ({i})".format(i=len(self)))
for i, site in enumerate(self):
- outs.append(" ".join([str(i + 1), site.species_string, " ".join([to_s(j).rjust(12) for j in site.frac_coords])]))
+ outs.append(" ".join([str(i + 1), site.species_string,
+ " ".join([to_s(j).rjust(12) for j in site.frac_coords])]))
return "\n".join(outs)
@property
@@ -1185,21 +1229,22 @@ def __init__(self, atomicspecies, coords, validate_proximity=False,
Defaults to None for no properties.
"""
if len(atomicspecies) != len(coords):
- raise StructureError("The list of atomic species must be of the same length as the list of fractional coordinates.")
+ raise StructureError(("The list of atomic species must be of the",
+ " same length as the list of fractional ",
+ "coordinates."))
- self._sites = []
+ sites = []
for i in xrange(len(atomicspecies)):
prop = None
if site_properties:
prop = {k:v[i] for k, v in site_properties.items()}
- self._sites.append(Site(atomicspecies[i], coords[i], properties=prop))
-
+ sites.append(Site(atomicspecies[i], coords[i], properties=prop))
if validate_proximity:
- for (s1, s2) in itertools.combinations(self._sites, 2):
+ for (s1, s2) in itertools.combinations(sites, 2):
if s1.distance(s2) < Structure.DISTANCE_TOLERANCE:
- raise StructureError("Molecule contains sites that are less than 0.01 Angstrom apart!")
-
- self._sites = tuple(self._sites)
+ raise StructureError(("Molecule contains sites that are ",
+ "less than 0.01 Angstrom apart!"))
+ self._sites = tuple(sites)
@property
def sites(self):
@@ -1221,7 +1266,8 @@ def __str__(self):
to_s = lambda x : "%0.6f" % x
outs.append("Sites ({i})".format(i=len(self)))
for i, site in enumerate(self):
- outs.append(" ".join([str(i + 1), site.species_string, " ".join([to_s(j).rjust(12) for j in site.coords])]))
+ outs.append(" ".join([str(i + 1), site.species_string,
+ " ".join([to_s(j).rjust(12) for j in site.coords])]))
return "\n".join(outs)
@property
@@ -1254,7 +1300,10 @@ def from_dict(d):
for site_dict in d['sites']:
sp = site_dict['species']
- species.append({ Specie(sp['element'], sp['oxidation_state']) if 'oxidation_state' in sp else Element(sp['element']) : sp['occu'] for sp in site_dict['species']})
+ species.append({ Specie(sp['element'], sp['oxidation_state']) \
+ if 'oxidation_state' in sp else \
+ Element(sp['element']) : sp['occu'] \
+ for sp in site_dict['species']})
coords.append(site_dict['xyz'])
siteprops = site_dict.get('properties', {})
for k, v in siteprops.items():
@@ -1315,7 +1364,8 @@ def get_neighbors(self, site, r):
[(site, dist) ...] since most of the time, subsequent processing
requires the distance.
"""
- return [(s, dist) for (s, dist) in self.get_sites_in_sphere(site.coords, r) if site != s]
+ nn = self.get_sites_in_sphere(site.coords, r)
+ return [(s, dist) for (s, dist) in nn if site != s]
def get_neighbors_in_shell(self, origin, r, dr):
"""
@@ -1362,7 +1412,10 @@ def get_boxed_structure(self, a, b, c):
if a <= x_range or b <= y_range or c <= z_range:
raise ValueError("Box is not big enough to contain Molecule.")
lattice = Lattice.from_parameters(a, b, c, 90, 90, 90)
- return Structure(lattice, self.species, self.cart_coords, coords_are_cartesian=True, site_properties=self.site_properties)
+ return Structure(lattice, self.species, self.cart_coords,
+ coords_are_cartesian=True,
+ site_properties=self.site_properties)
+
class StructureError(Exception):
'''
@@ -1409,7 +1462,8 @@ class Composition (collections.Mapping, collections.Hashable, MSONable):
"""
Tolerance in distinguishing different composition amounts.
- 1e-8 is fairly tight, but should cut out most floating point arithmetic errors.
+ 1e-8 is fairly tight, but should cut out most floating point arithmetic
+ errors.
"""
amount_tolerance = 1e-8
@@ -1417,7 +1471,8 @@ class Composition (collections.Mapping, collections.Hashable, MSONable):
Special formula handling for peroxides and certain elements. This is so that
formula output does not write LiO instead of Li2O2 for example.
"""
- special_formulas = {'LiO':'Li2O2', 'NaO':'Na2O2', 'KO':'K2O2', 'HO':'H2O2', 'O':'O2', 'F':'F2', 'N':'N2', 'Cl':'Cl2', 'H':'H2'}
+ special_formulas = {'LiO':'Li2O2', 'NaO':'Na2O2', 'KO':'K2O2', 'HO':'H2O2',
+ 'O':'O2', 'F':'F2', 'N':'N2', 'Cl':'Cl2', 'H':'H2'}
def __init__(self, *args, **kwargs):
"""
@@ -1427,10 +1482,10 @@ def __init__(self, *args, **kwargs):
Args:
Any form supported by the Python built-in dict() function.
- 1. A dict of either {Element/Specie: amount}, {string symbol:amount},
- or {atomic number:amount} or any mixture of these. E.g.,
- {Element("Li"):2 ,Element("O"):1}, {"Li":2, "O":1}, {3:2, 8:1}
- all result in a Li2O composition.
+ 1. A dict of either {Element/Specie: amount},
+ {string symbol:amount}, or {atomic number:amount} or any mixture
+ of these. E.g., {Element("Li"):2 ,Element("O"):1},
+ {"Li":2, "O":1}, {3:2, 8:1} all result in a Li2O composition.
2. Keyword arg initialization, similar to a dict, e.g.,
Compostion(Li = 2, O = 1)
@@ -1493,7 +1548,9 @@ def __sub__(self, other):
if el in self and other[k] <= self[el]:
new_el_map[el] -= other[k]
else:
- raise ValueError("All elements in subtracted composition must exist in original composition in equal or lesser amount!")
+ raise ValueError(("All elements in subtracted composition must",
+ " exist in original composition in equal or ",
+ "lesser amount!"))
return Composition(new_el_map)
def __mul__(self, other):
@@ -1502,7 +1559,7 @@ def __mul__(self, other):
Fe2O3 * 4 -> Fe8O12
"""
if not (isinstance(other, int) or isinstance(other, float)):
- raise ValueError("Multiplication of composition can only be done for integers or floats!")
+ raise ValueError("Multiplication can only be done for int/floats!")
return Composition({el:self[el] * other for el in self})
def __hash__(self):
@@ -1531,7 +1588,8 @@ def is_element(self):
'''
True if composition is for an element.
'''
- positive_amts = [amt for amt in self._elmap.values() if amt > self.amount_tolerance]
+ positive_amts = [amt for amt in self._elmap.values() \
+ if amt > self.amount_tolerance]
return len(positive_amts) == 1
def copy(self):
@@ -1590,7 +1648,8 @@ def get_reduced_formula_and_factor(self):
num_el = len(syms)
contains_polyanion = False
if num_el >= 3:
- contains_polyanion = (Element(syms[num_el - 1]).X - Element(syms[num_el - 2]).X < 1.65)
+ contains_polyanion = (Element(syms[num_el - 1]).X - \
+ Element(syms[num_el - 2]).X < 1.65)
factor = reduce(gcd, self._elmap.values())
reduced_form = ''
@@ -1637,7 +1696,8 @@ def get_reduced_formula_and_factor(self):
@property
def reduced_formula(self):
'''
- Returns a pretty normalized formula, i.e., LiFePO4 instead of Li4Fe4P4O16.
+ Returns a pretty normalized formula, i.e., LiFePO4 instead of
+ Li4Fe4P4O16.
'''
return self.get_reduced_formula_and_factor()[0]
@@ -1663,7 +1723,8 @@ def weight(self):
'''
Total molecular weight of Composition
'''
- return sum([amount * el.atomic_mass for el, amount in self._elmap.items()])
+ return sum([amount * el.atomic_mass \
+ for el, amount in self._elmap.items()])
def get_atomic_fraction(self, el):
'''
@@ -1717,7 +1778,8 @@ def get_sym_dict(f, factor):
if m.group(2) != "":
factor = float(m.group(2))
unit_sym_dict = get_sym_dict(m.group(1), factor)
- expanded_formula = formula.replace(m.group(), "".join([el + str(amt) for el, amt in unit_sym_dict.items()]))
+ expanded_sym = "".join(["{}{}".format(el, amt) for el, amt in unit_sym_dict.items()])
+ expanded_formula = formula.replace(m.group(), expanded_sym)
return self._parse_formula(expanded_formula)
return get_sym_dict(formula, 1)
@@ -1778,7 +1840,8 @@ def from_dict(d):
def to_dict(self):
'''
Returns:
- dict with element symbol and (unreduced) amount e.g. {"Fe": 4.0, "O":6.0}
+ dict with element symbol and (unreduced) amount e.g.,
+ {"Fe": 4.0, "O":6.0}
'''
d = {}
for e, a in self.items():
@@ -1792,7 +1855,8 @@ def to_dict(self):
def to_reduced_dict(self):
'''
Returns:
- dict with element symbol and reduced amount e.g. {"Fe": 2.0, "O":3.0}
+ dict with element symbol and reduced amount e.g.,
+ {"Fe": 2.0, "O":3.0}
'''
reduced_formula = self.reduced_formula
c = Composition.from_formula(reduced_formula)
@@ -1815,7 +1879,8 @@ def to_data_dict(self):
return d
@staticmethod
- def ranked_compositions_from_indeterminate_formula(fuzzy_formula, lock_if_strict=True):
+ def ranked_compositions_from_indeterminate_formula(fuzzy_formula,
+ lock_if_strict=True):
'''
Takes in a formula where capitilization might not be correctly entered,
and suggests a ranked list of potential Composition matches.
@@ -1834,9 +1899,11 @@ def ranked_compositions_from_indeterminate_formula(fuzzy_formula, lock_if_strict
A ranked list of potential Composition matches
'''
- #if we have an exact match and the user specifies lock_if_strict, just return the exact match!
+ #if we have an exact match and the user specifies lock_if_strict, just
+ #return the exact match!
if lock_if_strict:
- #the strict composition parsing might throw an error, we can ignore it and just get on with fuzzy matching
+ #the strict composition parsing might throw an error, we can ignore
+ #it and just get on with fuzzy matching
try:
comp = Composition.from_formula(fuzzy_formula)
return [comp]
@@ -1872,17 +1939,20 @@ def _recursive_compositions_from_fuzzy_formula(fuzzy_formula, m_dict={}, m_point
Returns:
A list of tuples, with the first element being a Composition and
- the second element being the number of points awarded that Composition intepretation
+ the second element being the number of points awarded that
+ Composition intepretation.
'''
def _parse_chomp_and_rank(m, f, m_dict, m_points):
'''
- A helper method for formula parsing that helps in interpreting and ranking indeterminate formulas
+ A helper method for formula parsing that helps in interpreting and
+ ranking indeterminate formulas
Author: Anubhav Jain
Arguments:
m:
- a regex match, with the first group being the element and the second group being the amount
+ a regex match, with the first group being the element and
+ the second group being the amount
f:
The formula part containing the match
m_dict:
@@ -1890,8 +1960,11 @@ def _parse_chomp_and_rank(m, f, m_dict, m_points):
m_points:
Number of points gained from the previously parsed formula
Returns:
- A tuple of (f, m_dict, points) where m_dict now contains data from the match and the match has been removed (chomped) from the formula f. The 'goodness'
- of the match determines the number of points returned for chomping. Returns (None, None, None) if no element could be found...
+ A tuple of (f, m_dict, points) where m_dict now contains data
+ from the match and the match has been removed (chomped) from
+ the formula f. The 'goodness' of the match determines the
+ number of points returned for chomping. Returns
+ (None, None, None) if no element could be found...
'''
points = 0
282 pymatgen/core/structure_modifier.py
View
@@ -20,7 +20,7 @@
import numpy as np
from pymatgen.core.periodic_table import Specie, Element
from pymatgen.core.lattice import Lattice
-from pymatgen.core.structure import Structure, PeriodicSite
+from pymatgen.core.structure import Structure, PeriodicSite, Site, Molecule
class StructureModifier(object):
@@ -43,6 +43,7 @@ def original_structure(self):
'''
return
+
class StructureEditor(StructureModifier):
"""
Editor for adding, removing and changing sites from a structure
@@ -97,12 +98,12 @@ def mod_site(site):
new_atom_occu = dict()
for sp, amt in site.species_and_occu.items():
if sp in species_mapping:
- if species_mapping[sp].__class__.__name__ in ('Element', 'Specie'):
+ if isinstance(species_mapping[sp], (Element, Specie)):
if species_mapping[sp] in new_atom_occu:
new_atom_occu[species_mapping[sp]] += amt
else:
new_atom_occu[species_mapping[sp]] = amt
- elif species_mapping[sp].__class__.__name__ == 'dict':
+ elif isinstance(species_mapping[sp], dict):
for new_sp, new_amt in species_mapping[sp].items():
if new_sp in new_atom_occu:
new_atom_occu[new_sp] += amt * new_amt
@@ -113,7 +114,8 @@ def mod_site(site):
new_atom_occu[sp] += amt
else:
new_atom_occu[sp] = amt
- return PeriodicSite(new_atom_occu, self._lattice.get_fractional_coords(site.coords), self._lattice)
+ return PeriodicSite(new_atom_occu, self._lattice.get_fractional_coords(site.coords),
+ self._lattice, properties=site.properties)
self._sites = map(mod_site, self._sites)
@@ -127,7 +129,9 @@ def replace_site(self, index, species_n_occu):
species:
A species object
"""
- self._sites[index] = PeriodicSite(species_n_occu, self._lattice.get_fractional_coords(self._sites[index].coords), self._lattice)
+ self._sites[index] = PeriodicSite(species_n_occu, self._lattice.get_fractional_coords(self._sites[index].coords),
+ self._lattice,
+ properties=self._sites[index].properties)
def remove_species(self, species):
"""
@@ -141,7 +145,9 @@ def remove_species(self, species):
for site in self._sites:
new_sp_occu = {sp:amt for sp, amt in site.species_and_occu.items() if sp not in species}
if len(new_sp_occu) > 0:
- new_sites.append(PeriodicSite(new_sp_occu, site.frac_coords, self._lattice))
+ new_sites.append(PeriodicSite(new_sp_occu, site.frac_coords,
+ self._lattice,
+ properties=site.properties))
self._sites = new_sites
def append_site(self, species, coords, coords_are_cartesian=False,
@@ -177,12 +183,15 @@ def insert_site(self, i, species, coords, coords_are_cartesian=False,
coords_are_cartesian:
Whether coordinates are cartesian. Defaults to False.
validate_proximity:
- Whether to check if inserted site is too close to an existing site. Defaults to True.
+ Whether to check if inserted site is too close to an existing
+ site. Defaults to True.
"""
if not coords_are_cartesian:
- new_site = PeriodicSite(species, coords, self._lattice, properties=properties)
+ new_site = PeriodicSite(species, coords, self._lattice,
+ properties=properties)
else:
- new_site = PeriodicSite(species, self._lattice.get_fractional_coords(coords), self._lattice, properties=properties)
+ new_site = PeriodicSite(species, self._lattice.get_fractional_coords(coords),
+ self._lattice, properties=properties)
if validate_proximity:
for site in self._sites:
@@ -224,7 +233,7 @@ def apply_operation(self, symmop):
self._lattice = Lattice([symmop.apply_rotation_only(row) for row in self._lattice.matrix])
def operate_site(site):
new_cart = symmop.operate(site.coords)
- return PeriodicSite(site.species_and_occu, self._lattice.get_fractional_coords(new_cart), self._lattice)
+ return PeriodicSite(site.species_and_occu, self._lattice.get_fractional_coords(new_cart), self._lattice, properties=site.properties)
self._sites = map(operate_site, self._sites)
def modify_lattice(self, new_lattice):
@@ -238,7 +247,10 @@ def modify_lattice(self, new_lattice):
self._lattice = new_lattice
new_sites = []
for site in self._sites:
- new_sites.append(PeriodicSite(site.species_and_occu, self._lattice.get_fractional_coords(site.coords), self._lattice))
+ new_sites.append(PeriodicSite(site.species_and_occu,
+ self._lattice.get_fractional_coords(site.coords),
+ self._lattice,
+ properties=site.properties))
self._sites = new_sites
def translate_sites(self, indices, vector, frac_coords=True):
@@ -261,7 +273,9 @@ def translate_sites(self, indices, vector, frac_coords=True):
fcoords = site.frac_coords + vector
else:
fcoords = self._lattice.get_fractional_coords(site.coords + vector)
- new_site = PeriodicSite(site.species_and_occu, fcoords, self._lattice, to_unit_cell=True, coords_are_cartesian=False)
+ new_site = PeriodicSite(site.species_and_occu, fcoords, self._lattice,
+ to_unit_cell=True, coords_are_cartesian=False,
+ properties=site.properties)
self._sites[i] = new_site
def perturb_structure(self, distance=0.1):
@@ -439,6 +453,250 @@ def modified_structure(self):
return self._modified_structure
+class MoleculeEditor(StructureModifier):
+ """
+ Editor for adding, removing and changing sites from a molecule
+ """
+ DISTANCE_TOLERANCE = 0.01
+
+ def __init__(self, molecule):
+ """
+ Args:
+ molecule:
+ pymatgen.core.structure Molecule object.
+ """
+ self._original_structure = molecule
+ self._sites = list(molecule.sites)
+
+ def add_site_property(self, property_name, values):
+ """
+ Adds a property to a site.
+
+ Args:
+ property_name:
+ The name of the property to add.
+ values:
+ A sequence of values. Must be same length as number of sites.
+ """
+ if len(values) != len(self._sites):
+ raise ValueError("Values must be same length as sites.")
+ for i in xrange(len(self._sites)):
+ site = self._sites[i]
+ props = site.properties
+ if not props:
+ props = {}
+ props[property_name] = values[i]
+ self._sites[i] = Site(site.species_and_occu, site.coords, properties=props)
+
+ def replace_species(self, species_mapping):
+ """
+ Swap species in a molecule.
+
+ Args:
+ species_mapping:
+ dict of species to swap. Species can be elements too.
+ e.g., {Element("Li"): Element("Na")} performs a Li for Na
+ substitution. The second species can be a sp_and_occu dict.
+ For example, a site with 0.5 Si that is passed the mapping
+ {Element('Si): {Element('Ge'):0.75, Element('C'):0.25} } will
+ have .375 Ge and .125 C.
+ """
+
+ def mod_site(site):
+ new_atom_occu = dict()
+ for sp, amt in site.species_and_occu.items():
+ if sp in species_mapping:
+ if isinstance(species_mapping[sp], (Element, Specie)):
+ if species_mapping[sp] in new_atom_occu:
+ new_atom_occu[species_mapping[sp]] += amt
+ else:
+ new_atom_occu[species_mapping[sp]] = amt
+ elif isinstance(species_mapping[sp], dict):
+ for new_sp, new_amt in species_mapping[sp].items():
+ if new_sp in new_atom_occu:
+ new_atom_occu[new_sp] += amt * new_amt
+ else:
+ new_atom_occu[new_sp] = amt * new_amt
+ else:
+ if sp in new_atom_occu:
+ new_atom_occu[sp] += amt
+ else:
+ new_atom_occu[sp] = amt
+ return Site(new_atom_occu, site.coords, properties=site.properties)
+ self._sites = map(mod_site, self._sites)
+
+ def replace_site(self, index, species_n_occu):
+ """
+ Replace a single site. Takes either a species or a dict of occus
+
+ Args:
+ index:
+ The index of the site in the _sites list
+ species:
+ A species object
+ """
+ self._sites[index] = Site(species_n_occu, self._sites[index].coords, properties=self._sites[index].properties)
+
+ def remove_species(self, species):
+ """
+ Remove all occurrences of a species from a molecule.
+
+ Args:
+ species:
+ species to remove
+ """
+ new_sites = []
+ for site in self._sites:
+ new_sp_occu = {sp:amt for sp, amt in site.species_and_occu.items() if sp not in species}
+ if len(new_sp_occu) > 0:
+ new_sites.append(Site(new_sp_occu, site.coords, properties=site.properties))
+ self._sites = new_sites
+
+ def append_site(self, species, coords, validate_proximity=True):
+ """
+ Append a site to the structure at the end.
+
+ Args:
+ species:
+ species of inserted site
+ coords:
+ coordinates of inserted site
+ validate_proximity:
+ Whether to check if inserted site is too close to an existing
+ site. Defaults to True.
+ """
+ self.insert_site(len(self._sites), species, coords, validate_proximity)
+
+ def insert_site(self, i, species, coords, validate_proximity=True, properties=None):
+ """
+ Insert a site to the structure.
+
+ Args:
+ i:
+ index to insert site
+ species:
+ species of inserted site
+ coords:
+ coordinates of inserted site
+ validate_proximity:
+ Whether to check if inserted site is too close to an existing site. Defaults to True.
+ """
+ new_site = Site(species, coords, properties=properties)
+
+ if validate_proximity:
+ for site in self._sites:
+ if site.distance(new_site) < self.DISTANCE_TOLERANCE:
+ raise ValueError("New site is too close to an existing site!")
+ self._sites.insert(i, new_site)
+
+ def delete_site(self, i):
+ """
+ Delete site at index i.
+
+ Args:
+ i:
+ index of site to delete.
+ """
+ del(self._sites[i])
+
+ def delete_sites(self, indices):
+ """
+ Delete sites with at indices.
+
+ Args:
+ indices:
+ sequence of indices of sites to delete.
+ """
+ self._sites = [self._sites[i] for i in range(len(self._sites)) if i not in indices]
+
+ def translate_sites(self, indices, vector):
+ """
+ Translate specific sites by some vector, keeping the sites within the
+ unit cell.
+
+ Args:
+ sites:
+ List of site indices on which to perform the translation.
+ vector:
+ Translation vector for sites.
+ """
+ for i in indices:
+ site = self._sites[i]
+ new_site = Site(site.species_and_occu, site.coords + vector, properties=site.properties)
+ self._sites[i] = new_site
+
+ def perturb_structure(self, distance=0.1):
+ '''
+ Performs a random perturbation of the sites in a structure to break
+ symmetries.
+
+ Args:
+ distance:
+ distance in angstroms by which to perturb each site.
+ '''
+ for i in range(len(self._sites)):
+ vector = np.random.rand(3)
+ vector /= np.linalg.norm(vector) / distance
+ self.translate_sites([i], vector)
+
+ @property
+ def original_structure(self):
+ return self._original_structure
+
+ @property
+ def modified_structure(self):
+ coords = [site.coords for site in self._sites]
+ species = [site.species_and_occu for site in self._sites]
+ props = {}
+ if self._sites[0].properties:
+ for k in self._sites[0].properties.keys():
+ props[k] = [site.properties[k] for site in self._sites]
+ return Molecule(species, coords, False, site_properties=props)
+
+
+class SupercellMaker(StructureModifier):
+ """
+ Makes a supercell
+ """
+
+ def __init__(self, structure, scaling_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1))):
+ """
+ Create a supercell.
+
+ Args:
+ structure:
+ pymatgen.core.structure Structure object.
+ scaling_matrix:
+ a matrix of transforming the lattice vectors. Defaults to the
+ identity matrix. Has to be all integers. e.g.,
+ [[2,1,0],[0,3,0],[0,0,1]] generates a new structure with
+ lattice vectors a' = 2a + b, b' = 3b, c' = c where a, b, and c
+ are the lattice vectors of the original structure.
+ """
+ self._original_structure = structure
+ old_lattice = structure.lattice
+ scale_matrix = np.array(scaling_matrix)
+ new_lattice = Lattice(np.dot(scale_matrix, old_lattice.matrix))
+ new_species = []
+ new_fcoords = []
+ def range_vec(i):
+ return range(max(scale_matrix[:][:, i]) - min(scale_matrix[:][:, i]))
+ for site in structure.sites:
+ for (i, j, k) in itertools.product(range_vec(0), range_vec(1), range_vec(2)):
+ new_species.append(site.species_and_occu)
+ fcoords = site.frac_coords
+ coords = old_lattice.get_cartesian_coords(fcoords + np.array([i, j, k]))
+ new_fcoords.append(new_lattice.get_fractional_coords(coords))
+ self._modified_structure = Structure(new_lattice, new_species, new_fcoords, False)
+
+ @property
+ def original_structure(self):
+ return self._original_structure
+
+ @property
+ def modified_structure(self):
+ return self._modified_structure
+
if __name__ == "__main__":
import doctest
doctest.testmod()
2  pymatgen/core/tests/test_structure.py
View
@@ -234,6 +234,7 @@ def test_get_dist_matrix(self):
[ 2.3516318, 0.]]
self.assertTrue(np.allclose(self.struct.distance_matrix, ans))
+
class MoleculeTest(unittest.TestCase):
def setUp(self):
@@ -307,6 +308,7 @@ def test_to_from_dict(self):
self.assertEqual(d['sites'][0]['properties']['magmom'], 0.5)
mol = Molecule.from_dict(d)
self.assertEqual(mol[0].magmom, 0.5)
+ self.assertEqual(mol.formula, "H4 C1")
def test_get_boxed_structure(self):
s = self.mol.get_boxed_structure(9, 9, 9)
84 pymatgen/core/tests/test_structure_modifier.py
View
@@ -4,14 +4,14 @@
from pymatgen.core.periodic_table import Element, Specie
from pymatgen.core.lattice import Lattice
-from pymatgen.core.structure import Structure
-from pymatgen.core.structure_modifier import StructureEditor, SupercellMaker, OxidationStateDecorator, OxidationStateRemover
+from pymatgen.core.structure import Structure, Molecule
+from pymatgen.core.structure_modifier import StructureEditor, SupercellMaker, OxidationStateDecorator, OxidationStateRemover, MoleculeEditor
import numpy as np
+
class StructureEditorTest(unittest.TestCase):
def setUp(self):
-
self.si = Element("Si")
self.fe = Element("Fe")
self.ge = Element("Ge")
@@ -19,8 +19,16 @@ def setUp(self):
coords.append(np.array([0, 0, 0]))
coords.append(np.array([0.75, 0.5, 0.75]))
lattice = Lattice.cubic(10)
- s = Structure(lattice, [self.si, self.fe], coords)
+ s = Structure(lattice, ["Si", "Fe"], coords)
self.modifier = StructureEditor(s)
+ coords = [[0.000000, 0.000000, 0.000000],
+ [0.000000, 0.000000, 1.089000],
+ [1.026719, 0.000000, -0.363000],
+ [-0.513360, -0.889165, -0.363000],
+ [-0.513360 , 0.889165 , -0.363000]]
+ mol = Molecule(["C", "H", "H", "H", "H"], coords)
+ #self.modifier = StructureEditor(mol)
+
def test_translate_sites(self):
self.modifier.translate_sites([0, 1], [0.5, 0.5, 0.5], frac_coords=True)
@@ -76,6 +84,7 @@ def test_add_site_property(self):
self.assertEqual(s[0].charge, 4.1)
self.assertEqual(s[0].magmom, 3)
+
class SupercellMakerTest(unittest.TestCase):
def setUp(self):
@@ -110,6 +119,7 @@ def test_modified_structure(self):
mod_s = self.mod.modified_structure
self.assertEqual(self.s_elem, mod_s, "Oxidation state remover failed")
+
class OxidationStateDecoratorTest(unittest.TestCase):
def setUp(self):
@@ -133,6 +143,72 @@ def test_modified_structure(self):
for k in site.species_and_occu.keys():
self.assertEqual(k.oxi_state, self.oxidation_states[k.symbol], "Wrong oxidation state assigned!")
+
+class MoleculeEditorTest(unittest.TestCase):
+
+ def setUp(self):
+ coords = [[0.000000, 0.000000, 0.000000],
+ [0.000000, 0.000000, 1.089000],
+ [1.026719, 0.000000, -0.363000],
+ [-0.513360, -0.889165, -0.363000],
+ [-0.513360 , 0.889165 , -0.363000]]
+ mol = Molecule(["C", "H", "H", "H", "H"], coords)
+ self.modifier = MoleculeEditor(mol)
+
+
+ def test_translate_sites(self):
+ self.modifier.translate_sites([0, 1], [0.5, 0.5, 0.5])
+ self.assertTrue(np.array_equal(self.modifier.modified_structure.cart_coords[0], np.array([ 0.5, 0.5, 0.5])))
+
+ def test_append_site(self):
+ self.modifier.append_site("Si", [0, 0.5, 0])
+ self.assertEqual(self.modifier.modified_structure.formula, "Si1 H4 C1", "Wrong formula!")
+ self.assertRaises(ValueError, self.modifier.append_site, Element("Si"), np.array([0, 0.5, 0]))
+
+ def test_modified_structure(self):
+ self.modifier.insert_site(1, "Si", [0, 0.25, 0])
+ self.assertEqual(self.modifier.modified_structure.formula, "Si1 H4 C1", "Wrong formula!")
+
+ self.modifier.delete_site(0)
+ self.assertEqual(self.modifier.modified_structure.formula, "Si1 H4", "Wrong formula!")
+
+ self.modifier.replace_site(0, "Ge")
+ self.assertEqual(self.modifier.modified_structure.formula, "Ge1 H4", "Wrong formula!")
+
+ self.modifier.append_site("Si", [0, 0.75, 0])
+ self.modifier.replace_species({Element("Si"): Element("Ge")})
+
+ self.assertEqual(self.modifier.modified_structure.formula, "Ge2 H4", "Wrong formula!")
+
+ self.modifier.replace_species({Element("Ge"): {Element("Ge"):0.5, Element("Si"):0.5}})
+ self.assertEqual(self.modifier.modified_structure.formula, "Si1 Ge1 H4", "Wrong formula!")
+
+ #this should change the .5Si .5Ge sites to .75Si .25Ge
+ self.modifier.replace_species({Element("Ge"): {Element("Ge"):0.5, Element("Si"):0.5}})
+ self.assertEqual(self.modifier.modified_structure.formula, "Si1.5 Ge0.5 H4", "Wrong formula!")
+
+ d = 0.1
+ pre_perturbation_sites = self.modifier.modified_structure.sites
+ self.modifier.perturb_structure(distance=d)
+ post_perturbation_sites = self.modifier.modified_structure.sites
+
+ for i, x in enumerate(pre_perturbation_sites):
+ self.assertAlmostEqual(x.distance(post_perturbation_sites[i]), d, 3, "Bad perturbation distance")
+
+ def test_add_site_property(self):
+ self.modifier.add_site_property("charge", [4.1, -2, -2, -2, -2])
+ s = self.modifier.modified_structure
+ self.assertEqual(s[0].charge, 4.1)
+ self.assertEqual(s[1].charge, -2)
+
+ #test adding multiple properties.
+ mod2 = MoleculeEditor(s)
+ mod2.add_site_property("magmom", [3, 2, 2, 2, 2])
+ s = mod2.modified_structure
+ self.assertEqual(s[0].charge, 4.1)
+ self.assertEqual(s[0].magmom, 3)
+
+
if __name__ == '__main__':
unittest.main()
Please sign in to comment.
Something went wrong with that request. Please try again.