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

Add supercell to UEP clustering #78 #79

Merged
merged 5 commits into from
Jun 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
83 changes: 51 additions & 32 deletions pymuonsuite/io/castep.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import numpy as np
import scipy.constants as cnst

from ase import io
from ase import Atoms, io
from ase.calculators.castep import Castep
from ase.io.castep import read_param, write_param
from ase.io.magres import read_magres
Expand Down Expand Up @@ -177,48 +177,67 @@ def write(self, a, folder, sname=None, calc_type="GEOM_OPT"):
| calc_type (str): Castep task which will be performed:
| "GEOM_OPT" or "MAGRES"
"""
if calc_type == "GEOM_OPT" or calc_type == "MAGRES":
if sname is None:
sname = os.path.split(folder)[-1] # Same as folder name
if sname is None:
sname = os.path.split(folder)[-1] # Same as folder name

self._calc = deepcopy(self._calc)
self.write_cell(a, folder, sname, calc_type)

# We only use the calculator attached to the atoms object if a calc
# has not been set when initialising the ReadWrite object OR we
# have not called write() and made a calculator before.
write_param(
os.path.join(folder, sname + ".param"),
a.calc.param,
force_write=True,
)

if self._calc is None:
if isinstance(a.calc, Castep):
self._calc = deepcopy(a.calc)
self._create_calculator(calc_type=calc_type)
else:
self._update_calculator(calc_type)
a.calc = self._calc
with silence_stdio():
io.write(
os.path.join(folder, sname + ".cell"),
a,
magnetic_moments="initial",
)
write_param(
os.path.join(folder, sname + ".param"),
a.calc.param,
force_write=True,
)
if self.script is not None:
stxt = open(self.script).read()
stxt = stxt.format(seedname=sname)
with open(os.path.join(folder, "script.sh"), "w", newline="\n") as sf:
sf.write(stxt)

if self.script is not None:
stxt = open(self.script).read()
stxt = stxt.format(seedname=sname)
with open(os.path.join(folder, "script.sh"), "w", newline="\n") as sf:
sf.write(stxt)
else:
def write_cell(
self, a: Atoms, folder: str, sname: str, calc_type: str = "GEOM_OPT"
):
"""Writes only the cell file for an Atoms object, but includes settings
from the Castep calculator.

| Args:
| a (ase.Atoms): Atoms object to write. Can have a Castep
| calculator attached to carry cell/param
| keywords.
| folder (str): Path to save the input files to.
| sname (str): Seedname to save the files with. If not
| given, use the name of the folder.
| calc_type (str): Castep task which will be performed:
| "GEOM_OPT" or "MAGRES"
"""
if calc_type != "GEOM_OPT" and calc_type != "MAGRES":
raise (
NotImplementedError(
"Calculation type {} is not implemented."
" Please choose 'GEOM_OPT' or 'MAGRES'".format(calc_type)
)
)

self._calc = deepcopy(self._calc)

# We only use the calculator attached to the atoms object if a calc
# has not been set when initialising the ReadWrite object OR we
# have not called write() and made a calculator before.
if self._calc is None:
if isinstance(a.calc, Castep):
self._calc = deepcopy(a.calc)
self._create_calculator(calc_type=calc_type)
else:
self._update_calculator(calc_type)
a.calc = self._calc

with silence_stdio():
io.write(
os.path.join(folder, sname + ".cell"),
a,
magnetic_moments="initial",
)

def _create_calculator(self, calc_type=None):
with silence_stdio():
if self._calc is not None and isinstance(self._calc, Castep):
Expand Down
132 changes: 68 additions & 64 deletions pymuonsuite/io/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@


import os
from ase import io
from ase import Atoms, io
import numpy as np
from datetime import datetime
from scipy.constants import physical_constants as pcnst

from pymuonsuite.utils import safe_create_folder
from pymuonsuite.utils import make_muonated_supercell, safe_create_folder
from pymuonsuite.io.castep import ReadWriteCastep
from pymuonsuite.io.dftb import ReadWriteDFTB

Expand All @@ -34,7 +33,6 @@ def write_cluster_report(args, params, clusters):
)

with open(params["name"] + "_clusters.txt", "w") as f:

f.write(
"""
****************************
Expand Down Expand Up @@ -62,11 +60,9 @@ def write_cluster_report(args, params, clusters):
)

for name, cdata in clusters.items():

f.write("Clusters for {0}:\n".format(name))

if params["clustering_save_min"] or params["clustering_save_type"]:

if params["clustering_save_folder"] is not None:
clustering_save_path = safe_create_folder(
params["clustering_save_folder"]
Expand All @@ -79,7 +75,6 @@ def write_cluster_report(args, params, clusters):
raise RuntimeError("Could not create folder {0}")

for calc, clusts in cdata.items():

# Computer readable
fdat = open(
params["name"] + "_{0}_{1}_clusters.dat".format(name, calc),
Expand All @@ -94,7 +89,6 @@ def write_cluster_report(args, params, clusters):
min_energy_structs = []

for i, g in enumerate(cgroups):

f.write(
"\n\n\t-----------\n\tCluster "
"{0}\n\t-----------\n".format(i + 1)
Expand All @@ -111,6 +105,7 @@ def write_cluster_report(args, params, clusters):
f.write(
"\t{0:.2f}\t\t{1:.2f}\t\t{2:.2f}\n".format(Emin, Eavg, Estd)
)
structure = coll[np.argmin(E)].structures[0]

fdat.write(
"\t".join(
Expand All @@ -122,9 +117,9 @@ def write_cluster_report(args, params, clusters):
Emin,
Eavg,
Estd,
coll[np.argmin(E)].structures[0].positions[-1][0],
coll[np.argmin(E)].structures[0].positions[-1][1],
coll[np.argmin(E)].structures[0].positions[-1][2],
structure.positions[-1][0],
structure.positions[-1][1],
structure.positions[-1][2],
],
)
)
Expand All @@ -133,66 +128,26 @@ def write_cluster_report(args, params, clusters):

f.write(
"\n\tMinimum energy structure: {0}\n".format(
coll[np.argmin(E)].structures[0].info["name"]
structure.info["name"]
)
)

# update symbol and mass of defect in minimum energy structure
min_energy_struct = coll[np.argmin(E)].structures[0]
csp = min_energy_struct.get_chemical_symbols()[:-1] + [
params.get("mu_symbol", "H:mu")
]
min_energy_struct.set_array("castep_custom_species", np.array(csp))
masses = min_energy_struct.get_masses()[:-1]
masses = np.append(
masses,
params.get("particle_mass_amu", pcnst["muon mass in u"][0]),
)
min_energy_struct.set_masses(np.array(masses))

# Save minimum energy structure
if (
params["clustering_save_type"] == "structures"
or params["clustering_save_min"]
):
# For backwards-compatability with old pymuonsuite
# versions
if params["clustering_save_min"]:
if params["clustering_save_format"] is None:
params["clustering_save_format"] = "cif"

try:
calc_path = os.path.join(clustering_save_path, calc)
if not os.path.exists(calc_path):
os.mkdir(calc_path)
fname = "{0}_{1}_min_cluster_" "{2}.{3}".format(
params["name"],
calc,
i + 1,
params["clustering_save_format"],
)

with silence_stdio():
io.write(
os.path.join(calc_path, fname),
min_energy_struct,
)

except (io.formats.UnknownFileTypeError) as e:
print(
"ERROR: File format '{0}' is not "
"recognised. Modify 'clustering_save_format'"
" and try again.".format(e)
)
return
except ValueError as e:
print(
"ERROR: {0}. Modify 'clustering_save_format'"
"and try again.".format(e)
)
success = write_structure(
params,
clustering_save_path,
calc,
f"{params['name']}_{calc}_min_cluster_{i+1}",
structure,
)
if not success:
return

min_energy_structs.append(min_energy_struct)
min_energy_structs.append(structure)

f.write("\n\n\tStructure list:")

Expand Down Expand Up @@ -270,10 +225,60 @@ def write_cluster_report(args, params, clusters):
f.write("\n==========================\n\n")


def write_phonon_report(args, params, phdata):
def write_structure(
params: dict, clustering_save_path: str, calc: str, seedname: str, structure: Atoms
) -> bool:
"""Writes the minimal energy structure in a cluster to file.

| Args:
| params (dict): PyMuonSuite MUAIRSS parameters.
| clustering_save_path (str): Directory to save the clusters in.
| calc (str): Calculator used for the optimisation.
| seedname (str): Seedname for the saved structure files.
| structure (Atoms): ASE Atoms to save.
|
| Returns:
| (bool): Whether the structure was written to file successfully.
"""
# For backwards-compatibility with old pymuonsuite versions
if params["clustering_save_min"]:
if params["clustering_save_format"] is None:
params["clustering_save_format"] = "cif"

try:
calc_path = os.path.join(clustering_save_path, calc)
if not os.path.exists(calc_path):
os.mkdir(calc_path)

with silence_stdio():
if calc == "uep":
structure = make_muonated_supercell(
structure, params["supercell"], params["mu_symbol"]
)

with open(params["name"] + "_phonons.txt", "w") as f:
if params["clustering_save_format"].lower() == "cell":
# Account for CASTEP calc settings
read_write_castep = ReadWriteCastep(params)
read_write_castep.write_cell(structure, calc_path, seedname)
else:
filename = f"{seedname}.{params['clustering_save_format']}"
io.write(os.path.join(calc_path, filename), structure)

return True

except io.formats.UnknownFileTypeError as e:
print(
f"ERROR: File format '{e}' is not recognised. "
"Modify 'clustering_save_format' and try again."
)
return False
except ValueError as e:
print(f"ERROR: {e}. Modify 'clustering_save_format' and try again.")
return False


def write_phonon_report(args, params, phdata):
with open(params["name"] + "_phonons.txt", "w") as f:
f.write(
"""
****************************
Expand Down Expand Up @@ -322,7 +327,6 @@ def write_phonon_report(args, params, phdata):


def write_symmetry_report(args, symdata, wpoints, fpos):

print("Wyckoff points symmetry report for {0}".format(args.structure))
print("Space Group International Symbol: " "{0}".format(symdata["international"]))
print("Space Group Hall Number: " "{0}".format(symdata["hall_number"]))
Expand Down
11 changes: 2 additions & 9 deletions pymuonsuite/muairss.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@
from spglib import find_primitive

from ase import Atoms, io
from ase.build import make_supercell
from soprano.utils import customize_warnings, silence_stdio
from soprano.collection import AtomsCollection
from soprano.collection.generate import defectGen
from soprano.analyse.phylogen import PhylogenCluster, Gene
from soprano.rnd import Random

from pymuonsuite.utils import (
make_3x3,
make_supercell,
safe_create_folder,
get_element_from_custom_symbol,
)
Expand Down Expand Up @@ -64,13 +63,7 @@ def generate_muairss_collection(struct, params):
)

# Make a supercell
sm = make_3x3(params["supercell"])
# ASE's make_supercell is weird, avoid if not necessary...
smdiag = np.diag(sm).astype(int)
if np.all(np.diag(smdiag) == sm):
scell0 = struct.repeat(smdiag)
else:
scell0 = make_supercell(struct, sm)
scell0 = make_supercell(struct, params["supercell"])

reduced_struct = find_primitive_structure(struct)

Expand Down