Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Defect: Allow bulk SC calculation to be skipped. #742

Merged
merged 73 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
70b29b1
start electrodes
jmmshn Dec 20, 2023
25cfc9a
start electrode
jmmshn Dec 20, 2023
6cec20e
start electrode
jmmshn Dec 20, 2023
9b66d67
start electrode
jmmshn Dec 20, 2023
68aab1a
start electrode
jmmshn Dec 20, 2023
3a30efa
start electrode
jmmshn Dec 20, 2023
5921016
VASP electrode job
jmmshn Dec 20, 2023
5495c87
VASP electrode job
jmmshn Dec 20, 2023
98b9b29
lint
jmmshn Dec 20, 2023
df07747
n steps
jmmshn Dec 20, 2023
638d784
n steps
jmmshn Dec 20, 2023
5320449
n steps
jmmshn Dec 20, 2023
1862501
n steps
jmmshn Dec 20, 2023
b9825f3
rm defect changes
jmmshn Dec 20, 2023
982463d
rm defect changes
jmmshn Dec 20, 2023
8f7c7bf
update
jmmshn Dec 20, 2023
5f1316e
update
jmmshn Dec 20, 2023
f60c5ad
update
jmmshn Dec 20, 2023
db76d90
update structure matcher
jmmshn Dec 21, 2023
f084145
debugging
jmmshn Dec 22, 2023
8b44900
debugging
jmmshn Dec 22, 2023
27d6944
debugging
jmmshn Dec 22, 2023
aca956c
debugging
jmmshn Dec 22, 2023
c098638
debugging
jmmshn Dec 22, 2023
76afa32
append names
jmmshn Jan 2, 2024
5b65a4a
append names
jmmshn Jan 2, 2024
2ac0fb2
append names
jmmshn Jan 2, 2024
d2f1643
append names
jmmshn Jan 2, 2024
0e1a069
append names
jmmshn Jan 2, 2024
e96652f
dev script change
jmmshn Jan 3, 2024
9cc4705
working test
jmmshn Jan 4, 2024
0551925
Merge remote-tracking branch 'mp/main' into js_runs
jmmshn Jan 4, 2024
2a7b36d
typo
jmmshn Jan 4, 2024
217b7a1
lint
jmmshn Jan 4, 2024
25cc070
lint
jmmshn Jan 4, 2024
140b973
lint
jmmshn Jan 4, 2024
d9b73f2
lint
jmmshn Jan 4, 2024
098722f
allow different bulk relax
jmmshn Jan 5, 2024
de48025
update
jmmshn Jan 9, 2024
f31781c
update
jmmshn Jan 9, 2024
0b53ecb
update
jmmshn Jan 10, 2024
5bd1726
update
jmmshn Jan 10, 2024
793fdf1
hydrogen
jmmshn Jan 11, 2024
f366294
Merge remote-tracking branch 'mp/main' into js_runs
jmmshn Jan 11, 2024
7e38d37
update emmet
jmmshn Jan 11, 2024
50e6b97
Merge branch 'main' into electrode
jmmshn Jan 11, 2024
934b04d
ulid tests
jmmshn Jan 23, 2024
515091b
lint
jmmshn Jan 23, 2024
c7fbc94
emmet
jmmshn Jan 23, 2024
ab43fb0
Merge remote-tracking branch 'mp/main' into js_runs
jmmshn Feb 6, 2024
f303c3d
Merge branch 'patch_defect' into js_runs
jmmshn Feb 7, 2024
919f9fd
uc_bulk
jmmshn Feb 23, 2024
5be2514
Merge remote-tracking branch 'mp/main' into defect_patch
jmmshn Feb 23, 2024
0189a13
Merge remote-tracking branch 'mp/main' into defect_patch
jmmshn Feb 26, 2024
dfe4a43
update docs
jmmshn Feb 26, 2024
dda9330
Merge remote-tracking branch 'mp/main' into defect_patch
jmmshn Feb 26, 2024
6e0c1fa
Merge branch 'defect_patch' into js_runs
jmmshn Feb 26, 2024
75377b1
update docs
jmmshn Feb 26, 2024
b049be2
update depent
jmmshn Feb 29, 2024
177d2f9
Merge remote-tracking branch 'mp/main' into defect_patch
jmmshn Mar 5, 2024
146c8e0
Merge branch 'defect_patch' into js_runs
jmmshn Mar 5, 2024
14c114b
Merge branch 'main' into defect_patch
jmmshn Mar 5, 2024
e820522
Merge remote-tracking branch 'mp/main' into defect_patch
jmmshn Mar 8, 2024
cea3e83
Merge branch 'defect_patch' into js_runs
jmmshn Mar 8, 2024
f36a8a0
Merge remote-tracking branch 'mp/main' into js_runs
jmmshn Mar 22, 2024
5f917b3
Merge remote-tracking branch 'mp/main' into defect_patch
jmmshn Mar 22, 2024
b22c275
Merge remote-tracking branch 'mp/main' into js_runs
jmmshn Mar 25, 2024
46fd376
Merge branch 'defect_patch' into js_runs
jmmshn Mar 25, 2024
fd1090d
Merge branch 'main' into defect_patch
jmmshn May 14, 2024
97530c5
get charge state calcs ASAP
jmmshn May 14, 2024
40a606f
Merge remote-tracking branch 'mp/main' into defect_patch
jmmshn May 28, 2024
55284b3
Merge branch 'main' into defect_patch
jmmshn Jun 19, 2024
8ad242c
Merge branch 'main' into defect_patch
utf Jul 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 177 additions & 39 deletions src/atomate2/common/flows/defect.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,116 @@ def make(
dir2 = relax2.output.dir_name
struct1 = relax1.output.structure
struct2 = relax2.output.structure
add_info1 = {"relaxed_uuid": relax1.uuid, "distorted_uuid": relax2.uuid}
add_info2 = {"relaxed_uuid": relax2.uuid, "distorted_uuid": relax1.uuid}

deformations1, deformations2, ccd_job = self.get_deformation_and_ccd_jobs(
struct1, struct2, dir1, dir2, add_info1, add_info2
)

return Flow(
jobs=[
charged_structures,
relax1,
relax2,
deformations1,
deformations2,
ccd_job,
],
output=ccd_job.output,
name=name,
)

def make_from_relaxed_structures(
self,
structure1: Structure,
structure2: Structure,
) -> Flow:
"""
Make a job for the calculation of the configuration coordinate diagram.

Parameters
----------
structure1
The relaxed structure for charge state 1.
structure2
The relaxed structure for charge state 2.

Returns
-------
Flow
The full workflow for the calculation of the configuration coordinate
diagram.
"""
# use a more descriptive name when possible
if not isinstance(structure1, OutputReference):
name = f"{self.name}: {structure1.formula}"
if not (
isinstance(structure1, OutputReference)
or isinstance(structure2, OutputReference)
):
name = (
f"{self.name}: {structure1.formula}"
"({structure1.charge}-{structure2.charge})"
)

deformations1, deformations2, ccd_job = self.get_deformation_and_ccd_jobs(
structure1, structure2
)

return Flow(
jobs=[
deformations1,
deformations2,
ccd_job,
],
output=ccd_job.output,
name=name,
)

def get_deformation_and_ccd_jobs(
self,
struct1: Structure,
struct2: Structure,
dir1: str | None = None,
dir2: str | None = None,
add_info1: dict | None = None,
add_info2: dict | None = None,
) -> tuple[Job, Job, Job]:
"""Get the deformation and CCD jobs for the given structures.

Parameters
----------
struct1: Structure
The first structure.
struct2: Structure
The second structure.
dir1: str
The directory of the first structure.
dir2: str
The directory of the second structure.
add_info1: dict
Additional information to write
add_info2: dict
Additional information to write

Returns
-------
deformations1: Job
The deformation job for the first structure.
deformations2: Job
The deformation job for the second structure.
ccd_job: Job
The Job to construct the CCD document.
"""
deformations1 = spawn_energy_curve_calcs(
struct1,
struct2,
distortions=self.distortions,
static_maker=self.static_maker,
prev_dir=dir1,
add_name="q1",
add_info={"relaxed_uuid": relax1.uuid, "distorted_uuid": relax2.uuid},
add_info=add_info1,
)

deformations2 = spawn_energy_curve_calcs(
Expand All @@ -124,7 +225,7 @@ def make(
static_maker=self.static_maker,
prev_dir=dir2,
add_name="q2",
add_info={"relaxed_uuid": relax2.uuid, "distorted_uuid": relax1.uuid},
add_info=add_info2,
)

deformations1.append_name(" q1")
Expand All @@ -139,18 +240,7 @@ def make(
deformations1.output, deformations2.output, undistorted_index=min_abs_index
)

return Flow(
jobs=[
charged_structures,
relax1,
relax2,
deformations1,
deformations2,
ccd_job,
],
output=ccd_job.output,
name=name,
)
return deformations1, deformations2, ccd_job


@dataclass
Expand All @@ -161,6 +251,15 @@ class FormationEnergyMaker(Maker, ABC):
this maker is the `defect_relax_maker` which contains the settings for the atomic
relaxations that each defect supercell will undergo.

This maker can be used as a stand-alone maker to calculate all of the data
needed to populate the `DefectEntry` object. However, for you can also use this
maker with `uc_bulk` set to True (also set `collect_defect_entry_data` to False
and `bulk_relax_maker` to None). This will skip the bulk supercell calculations
assuming that bulk unit cell calculations are of high enough quality to be used
directly. In these cases, the bulk SC electrostatic potentials need to be
constructed without running a separate bulk SC calculation. This is currently
implemented through the grid re-sampling tools in `mp-pyrho`.

Attributes
----------
defect_relax_maker: Maker
Expand Down Expand Up @@ -188,6 +287,10 @@ class FormationEnergyMaker(Maker, ABC):
ng_settings = dict(zip(params, ng + ngf))
relax_maker = update_user_incar_settings(relax_maker, ng_settings)

uc_bulk: bool
If True, skip the bulk supercell calculation and only perform the defect
supercell calculations. This is useful for large-scale defect databases.

name: str
The name of the flow created by this maker.

Expand Down Expand Up @@ -247,6 +350,7 @@ class FormationEnergyMaker(Maker, ABC):

defect_relax_maker: Maker
bulk_relax_maker: Maker | None = None
uc_bulk: bool = False
name: str = "formation energy"
relax_radius: float | str | None = None
perturb: float | None = None
Expand All @@ -256,8 +360,15 @@ class FormationEnergyMaker(Maker, ABC):
def __post_init__(self) -> None:
"""Apply post init updates."""
self.validate_maker()
if self.bulk_relax_maker is None:
self.bulk_relax_maker = self.defect_relax_maker
if self.uc_bulk:
if self.bulk_relax_maker is not None:
raise ValueError("bulk_relax_maker should be None when uc_bulk is True")
if self.collect_defect_entry_data:
raise ValueError(
"collect_defect_entry_data should be False when uc_bulk is True"
)
else:
self.bulk_relax_maker = self.bulk_relax_maker or self.defect_relax_maker

def make(
self,
Expand Down Expand Up @@ -292,27 +403,41 @@ def make(
The workflow to calculate the formation energy diagram.
"""
jobs = []
if bulk_supercell_dir is None:
get_sc_job = bulk_supercell_calculation(
uc_structure=defect.structure,
relax_maker=self.bulk_relax_maker,
sc_mat=supercell_matrix,
get_planar_locpot=self.get_planar_locpot,
)
sc_mat = get_sc_job.output["sc_mat"]
lattice = get_sc_job.output["sc_struct"].lattice
bulk_supercell_dir = get_sc_job.output["dir_name"]
if not self.uc_bulk:
if bulk_supercell_dir is None:
get_sc_job = bulk_supercell_calculation(
uc_structure=defect.structure,
relax_maker=self.bulk_relax_maker,
sc_mat=supercell_matrix,
get_planar_locpot=self.get_planar_locpot,
)
sc_mat = get_sc_job.output["sc_mat"]
lattice = get_sc_job.output["sc_struct"].lattice
bulk_supercell_dir = get_sc_job.output["dir_name"]
sc_uuid = get_sc_job.output["uuid"]
else:
# all additional reader functions need to be in this job
# b/c they might receive Response objects instead of data.
get_sc_job = get_supercell_from_prv_calc(
uc_structure=defect.structure,
prv_calc_dir=bulk_supercell_dir,
sc_entry_and_locpot_from_prv=self.sc_entry_and_locpot_from_prv,
sc_mat_ref=supercell_matrix,
)
sc_mat = get_sc_job.output["sc_mat"]
lattice = get_sc_job.output["lattice"]
sc_uuid = get_sc_job.output["uuid"]
jobs.append(get_sc_job)
else:
# all additional reader functions need to be in this job
# b/c they might receive Response objects instead of data.
get_sc_job = get_supercell_from_prv_calc(
uc_structure=defect.structure,
prv_calc_dir=bulk_supercell_dir,
sc_entry_and_locpot_from_prv=self.sc_entry_and_locpot_from_prv,
sc_mat_ref=supercell_matrix,
)
sc_mat = get_sc_job.output["sc_mat"]
lattice = get_sc_job.output["lattice"]
if bulk_supercell_dir is not None:
raise ValueError(
"bulk_supercell_dir should be None when uc_bulk is True."
"We will be using a uc bulk calculation, so no bulk supercell "
"is needed."
)
sc_mat = supercell_matrix
lattice = None
sc_uuid = None

spawn_output = spawn_defect_q_jobs(
defect=defect,
Expand All @@ -323,13 +448,26 @@ def make(
add_info={
"bulk_supercell_dir": bulk_supercell_dir,
"bulk_supercell_matrix": sc_mat,
"bulk_supercell_uuid": get_sc_job.uuid,
"bulk_supercell_uuid": sc_uuid,
},
relax_radius=self.relax_radius,
perturb=self.perturb,
validate_charge=self.validate_charge,
)
jobs.extend([get_sc_job, spawn_output])

if self.uc_bulk:
# run the function here so we can get the charge state
# calculations ASAP
response = spawn_output.function(
*spawn_output.function_args, **spawn_output.function_kwargs
)
jobs.append(response.replace)
output_ = response.output
else:
# execute this as job so you can string a single bulk sc with multiple
# defect scs
jobs.append(spawn_output)
output_ = spawn_output.output

if self.collect_defect_entry_data:
collection_job = get_defect_entry(
Expand All @@ -340,7 +478,7 @@ def make(

return Flow(
jobs=jobs,
output=spawn_output.output,
output=output_,
name=self.name,
)

Expand Down
5 changes: 3 additions & 2 deletions src/atomate2/common/jobs/defect.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ def get_planar_locpot(task_doc: TaskDoc) -> NDArray:
def spawn_defect_q_jobs(
defect: Defect,
relax_maker: RelaxMaker,
relaxed_sc_lattice: Lattice,
relaxed_sc_lattice: Lattice | None = None,
sc_mat: NDArray | None = None,
defect_index: int | str = "",
add_info: dict | None = None,
Expand Down Expand Up @@ -355,7 +355,8 @@ def spawn_defect_q_jobs(
sc_def_struct = defect.get_supercell_structure(
sc_mat=sc_mat, relax_radius=relax_radius, perturb=perturb
)
sc_def_struct.lattice = relaxed_sc_lattice
if relaxed_sc_lattice is not None:
sc_def_struct.lattice = relaxed_sc_lattice
if sc_mat is not None:
sc_mat = np.array(sc_mat).tolist()
for qq in defect.get_charge_states():
Expand Down
46 changes: 46 additions & 0 deletions tests/vasp/flows/test_defect.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,49 @@ def _check_plnr_locpot(name):
prv_dir = test_dir / "vasp/GaN_Mg_defect/bulk_relax/outputs"
flow2 = maker.make(defects[0], bulk_supercell_dir=prv_dir, defect_index=0)
_ = run_locally(flow2, create_folders=True, ensure_success=True)


def test_formation_energy_maker_uc(mock_vasp, clean_dir, test_dir, monkeypatch):
from jobflow import run_locally

# mapping from job name to directory containing test files
ref_paths = {
"relax Mg_Ga-0 q=-2": "GaN_Mg_defect/relax_Mg_Ga-0_q=-2",
"relax Mg_Ga-0 q=-1": "GaN_Mg_defect/relax_Mg_Ga-0_q=-1",
"relax Mg_Ga-0 q=0": "GaN_Mg_defect/relax_Mg_Ga-0_q=0",
"relax Mg_Ga-0 q=1": "GaN_Mg_defect/relax_Mg_Ga-0_q=1",
}

fake_run_vasp_kwargs = {
k: {"incar_settings": ["ISIF"], "check_inputs": ["incar"]} for k in ref_paths
}

# automatically use fake VASP and write POTCAR.spec during the test
mock_vasp(ref_paths, fake_run_vasp_kwargs)

struct = Structure.from_file(test_dir / "structures" / "GaN.cif")
defects = list(
SubstitutionGenerator().get_defects(
structure=struct, substitution={"Ga": ["Mg"]}
)
)

maker = FormationEnergyMaker(
relax_radius="auto",
perturb=0.1,
collect_defect_entry_data=False,
validate_charge=False,
uc_bulk=True,
)
flow = maker.make(
defects[0],
supercell_matrix=[[2, 2, 0], [2, -2, 0], [0, 0, 1]],
defect_index=0,
)

# run the flow and ensure that it finished running successfully
_ = run_locally(
flow,
create_folders=True,
ensure_success=True,
)
Loading