Skip to content

Commit

Permalink
Merge pull request #29 from Materials-Data-Science-and-Informatics/se…
Browse files Browse the repository at this point in the history
…t_point_defects

Add feature to set point defects
  • Loading branch information
NinadBhat authored Jun 18, 2024
2 parents dfe9249 + 2b78232 commit e61053c
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 47 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

Most important changes in each released version.

## v0.3.11

* Add options to manually set point defect concetration and number.

## v0.3.10

* Add option to manually set crystal structure parameters (lattice constant and crytal type).

## v0.3.9

* Add Dislocation identification and Grain identification.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "atomID"
version = "0.3.9"
version = "0.3.11"
description = "Python package to identify and annotate crystal structure data files"
authors = ["Ninad Bhat"]
readme = "README.md"
Expand Down
125 changes: 93 additions & 32 deletions src/atomid/annotate.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,76 +235,137 @@ def identify_point_defects(
species_ref=species_reference,
species_actual=species_actual,
)

if hasattr(self, "defects") is False:
self.defects = dict()

self.defects.update(defects)

return defects

def identify_line_defects(self) -> tuple[list, list]:
"""Identify line defects in the crystal structure.
def add_vacancy_information(self, concentration: float, number: int) -> None:
"""Add vacancy information to the system.
Parameters
----------
concentration : float
The concentration of vacancies.
number : int
The number of vacancies.
Returns
-------
tuple[list, list]
A tuple containing the burgers vectors and lengths of the dislocations.
None
"""
(burgers_vectors, lengths) = identify_dislocations(self.ovito_pipeline)
if hasattr(self, "defects") is False:
self.defects = {}

if len(burgers_vectors) == 0:
print("\033[91mNo dislocations found.\033[0m")
return None, None
self.defects.update(
{"vacancies": {"concentration": concentration, "count": number}}
)

return burgers_vectors, lengths
def add_interstitial_information(self, concentration: float, number: int) -> None:
"""Add interstitial information to the system.
def identify_grains(self) -> tuple[list, list]:
"""Identify grains in the crystal structure.
Parameters
----------
concentration : float
The concentration of interstitials.
number : int
The number of interstitials.
Returns
-------
tuple[list, list]
A tuple containing the orientations and angles of the grains.
None
"""
orientations, angles = identify_grain_orientations(self.ovito_pipeline)
if hasattr(self, "defects") is False:
self.defects = {}

return orientations, angles
self.defects.update(
{"interstitials": {"concentration": concentration, "count": number}}
)

def annotate_point_defects(
self, reference_data_file: str, ref_format: str, method: Optional[str] = None
) -> None:
"""Annotate defects in the crystal structure using the reference data file.
def add_substitution_information(self, concentration: float, number: int) -> None:
"""Add substitution information to the system.
Parameters
----------
reference_data_file : str
The name of the file to read
ref_format : str
The format of the file. If None, the format is guessed from the file extension
method : str
The method to use for defect identification
concentration : float
The concentration of substitutions.
number : int
The number of substitutions.
Returns
-------
None
"""
defects = self.identify_point_defects(reference_data_file, ref_format, method)
if hasattr(self, "defects") is False:
self.defects = {}

self.defects.update(
{"substitutions": {"concentration": concentration, "count": number}}
)

vacancies = defects.get("vacancies", {"count": 0, "fraction": 0})
interstitials = defects.get("interstitials", {"count": 0, "fraction": 0})
substitutions = defects.get("substitutions", {"count": 0, "fraction": 0})
def annotate_point_defects(self) -> None:
"""Annotate defects in the crystal structure using the reference data file.
Returns
-------
None
"""
if hasattr(self, "defects") is False:
self.defects = {}

defects = self.defects

vacancies = defects.get("vacancies", {"count": 0, "concentration": 0})
interstitials = defects.get("interstitials", {"count": 0, "concentration": 0})
substitutions = defects.get("substitutions", {"count": 0, "concentration": 0})
if vacancies["count"] > 0:
self.system.add_vacancy(
concentration=vacancies["fraction"], number=vacancies["count"]
concentration=vacancies["concentration"], number=vacancies["count"]
)

if interstitials["count"] > 0:
self.system.add_triples_for_interstitial_impurities(
conc_of_impurities=interstitials["fraction"],
conc_of_impurities=interstitials["concentration"],
no_of_impurities=interstitials["count"],
)

if substitutions["count"] > 0:
self.system.add_triples_for_substitutional_impurities(
conc_of_impurities=substitutions["fraction"],
conc_of_impurities=substitutions["concentration"],
no_of_impurities=substitutions["count"],
)

def identify_line_defects(self) -> tuple[list, list]:
"""Identify line defects in the crystal structure.
Returns
-------
tuple[list, list]
A tuple containing the burgers vectors and lengths of the dislocations.
"""
(burgers_vectors, lengths) = identify_dislocations(self.ovito_pipeline)

if len(burgers_vectors) == 0:
print("\033[91mNo dislocations found.\033[0m")
return None, None

return burgers_vectors, lengths

def identify_grains(self) -> tuple[list, list]:
"""Identify grains in the crystal structure.
Returns
-------
tuple[list, list]
A tuple containing the orientations and angles of the grains.
"""
orientations, angles = identify_grain_orientations(self.ovito_pipeline)

return orientations, angles

def write_to_file(self, filename: str, format: str = "ttl") -> None:
"""Write the annotated system to a file.
Expand Down
12 changes: 6 additions & 6 deletions src/atomid/point_defect_analysis/wigner_seitz_method.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def analyze_defects(
Returns
-------
dict
A dictionary containing the counts and fractions of vacancies, interstitials, and substitutions.
A dictionary containing the counts and concentration of vacancies, interstitials, and substitutions.
"""
reference_array = np.array(reference_positions)
actual_array = np.array(actual_positions)
Expand Down Expand Up @@ -93,7 +93,7 @@ def calculate_defects(
atom_position_count: np.ndarray,
substitution_count: np.ndarray,
) -> Dict:
"""Calculate the number and fraction of vacancies, interstitials, and substitutions.
"""Calculate the number and concentration of vacancies, interstitials, and substitutions.
Parameters
----------
Expand All @@ -107,7 +107,7 @@ def calculate_defects(
Returns
-------
dict
A dictionary containing the counts and fractions of vacancies, interstitials, and substitutions.
A dictionary containing the counts and concentration of vacancies, interstitials, and substitutions.
"""
vacancies = [
(i, tuple(pos))
Expand All @@ -128,15 +128,15 @@ def calculate_defects(
return {
"vacancies": {
"count": len(vacancies),
"fraction": len(vacancies) / len(reference_array),
"concentration": len(vacancies) / len(reference_array),
},
"interstitials": {
"count": len(interstitials),
"fraction": len(interstitials) / len(reference_array),
"concentration": len(interstitials) / len(reference_array),
},
"substitutions": {
"count": len(substitutions) - len(interstitials),
"fraction": (len(substitutions) - len(interstitials))
"concentration": (len(substitutions) - len(interstitials))
/ len(reference_array),
},
}
56 changes: 51 additions & 5 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from utils.compare_rdf import compare_graphs


class TestAnnotatePointDefects:
class TestAnnotateSystemTest:
"""Tests for the AnnotateCrystal class for Point defects."""

testing_dict = {
Expand Down Expand Up @@ -92,7 +92,7 @@ def test_write_defects(
annotate_crystal.identify_point_defects(
reference_crystal_file, ref_format="vasp"
)

annotate_crystal.annotate_point_defects()
annotate_crystal.write_to_file(f"{tmp_path}/annotated_output.ttl", "ttl")

assert os.path.exists(f"{tmp_path}/annotated_output.ttl")
Expand All @@ -110,9 +110,10 @@ def test_output_annotation(
annotate_crystal.identify_crystal_structure()
annotate_crystal.annotate_crystal_structure()

annotate_crystal.annotate_point_defects(
annotate_crystal.identify_point_defects(
reference_crystal_file, ref_format="vasp"
)
annotate_crystal.annotate_point_defects()
annotate_crystal.write_to_file(f"{tmp_path}/annotated_output.ttl", "ttl")

assert os.path.exists(f"{tmp_path}/annotated_output.ttl")
Expand All @@ -127,6 +128,10 @@ def test_output_annotation(

assert result


class TestAnnotatePointDefects:
"""Tests for the AnnotateCrystal class for Point defects."""

def test_set_lattice_constant(self) -> None:
annotate_crystal = AnnotateCrystal(
"tests/data/fcc/Al/no_defect/initial/Al.poscar", "vasp"
Expand All @@ -141,19 +146,60 @@ def test_set_crystal_structure(self) -> None:
annotate_crystal.set_crystal_structure("fcc")
assert annotate_crystal.crystal_type == "fcc"

def test_annotate_crystal_returns_error(self) -> None:
def test_annotate_returns_error_no_lattice_constant(self) -> None:
annotate_crystal = AnnotateCrystal()
annotate_crystal.read_crystal_structure_file(
"tests/data/fcc/Al/no_defect/initial/Al.poscar", format="vasp"
)
annotate_crystal.set_crystal_structure("fcc")

with pytest.raises(ValueError):
annotate_crystal.annotate_crystal_structure()

def test_annotate_returns_error_no_crystal_type(self) -> None:
annotate_crystal = AnnotateCrystal()
annotate_crystal.read_crystal_structure_file(
"tests/data/fcc/Al/no_defect/initial/Al.poscar", format="vasp"
)
annotate_crystal.set_lattice_constant(3.5)
with pytest.raises(ValueError):
annotate_crystal.set_crystal_structure("other")
annotate_crystal.annotate_crystal_structure()

def test_add_vacancy(self) -> None:
annotate_crystal = AnnotateCrystal()
annotate_crystal.read_crystal_structure_file(
"tests/data/fcc/Al/defect/vacancy/initial/Al_vacancy.poscar", format="vasp"
)
annotate_crystal.identify_crystal_structure()
annotate_crystal.annotate_crystal_structure()
annotate_crystal.add_vacancy_information(concentration=0.1, number=40)
assert annotate_crystal.defects["vacancies"]["count"] == 40
assert annotate_crystal.defects["vacancies"]["concentration"] == 0.1

def test_add_interstitial(self) -> None:
annotate_crystal = AnnotateCrystal()
annotate_crystal.read_crystal_structure_file(
"tests/data/fcc/Al/defect/interstitial/initial/Al_interstitial.poscar",
format="vasp",
)
annotate_crystal.identify_crystal_structure()
annotate_crystal.annotate_crystal_structure()
annotate_crystal.add_interstitial_information(concentration=0.1, number=40)
assert annotate_crystal.defects["interstitials"]["count"] == 40
assert annotate_crystal.defects["interstitials"]["concentration"] == 0.1

def test_add_substitution(self) -> None:
annotate_crystal = AnnotateCrystal()
annotate_crystal.read_crystal_structure_file(
"tests/data/fcc/Al/defect/substitution/initial/Al_substitution.poscar",
format="vasp",
)
annotate_crystal.identify_crystal_structure()
annotate_crystal.annotate_crystal_structure()
annotate_crystal.add_substitution_information(concentration=0.1, number=40)
assert annotate_crystal.defects["substitutions"]["count"] == 40
assert annotate_crystal.defects["substitutions"]["concentration"] == 0.1


class TestAnnotateGrains:
"""Tests for the AnnotateCrystal class for Grain boundaries."""
Expand Down
6 changes: 3 additions & 3 deletions tests/test_point_defect_identification.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def test_analyze_defects_vacacy() -> None:
)

assert defects["vacancies"]["count"] == 1
assert defects["vacancies"]["fraction"] == 0.2
assert defects["vacancies"]["concentration"] == 0.2


def test_analyze_defects_interstitial() -> None:
Expand All @@ -59,7 +59,7 @@ def test_analyze_defects_interstitial() -> None:
reference_positions=reference_position, actual_positions=actual_position
)
assert defects["interstitials"]["count"] == 1
assert defects["interstitials"]["fraction"] == 0.25
assert defects["interstitials"]["concentration"] == 0.25


def test_analyze_defects_substitution() -> None:
Expand All @@ -83,7 +83,7 @@ def test_analyze_defects_substitution() -> None:
reference_positions, actual_positions, species_ref, species_actual
)
assert defects["substitutions"]["count"] == 1
assert defects["substitutions"]["fraction"] == 0.25
assert defects["substitutions"]["concentration"] == 0.25


def test_create_index_finder_euclidean(reference_array: np.ndarray) -> None:
Expand Down

0 comments on commit e61053c

Please sign in to comment.