diff --git a/.ci_support/exclude b/.ci_support/exclude index c9a81af8..0ea90c70 100644 --- a/.ci_support/exclude +++ b/.ci_support/exclude @@ -1,4 +1,4 @@ -spgfit/01_Structures.ipynb -spgfit/02_Calculations.ipynb -spgfit/03_Learn.ipynb +assyst/01_Structures.ipynb +assyst/02_Calculations.ipynb +assyst/03_Learn.ipynb MTP_Example.ipynb diff --git a/.github/workflows/push-pull.yml b/.github/workflows/push-pull.yml index be7ce1db..50e8e160 100644 --- a/.github/workflows/push-pull.yml +++ b/.github/workflows/push-pull.yml @@ -13,3 +13,4 @@ jobs: secrets: inherit with: notebooks-env-files: .ci_support/environment.yml .ci_support/environment-notebooks.yml + python-version-alt3: exclude diff --git a/pyiron_potentialfit/__init__.py b/pyiron_potentialfit/__init__.py index c45beec4..d57ade8f 100644 --- a/pyiron_potentialfit/__init__.py +++ b/pyiron_potentialfit/__init__.py @@ -31,12 +31,10 @@ # for module in moved_potential_modules: add_module_conversion( - "pyiron_potentialfit.spgfit.structures", - "pyiron_potentialfit.assyst.structures" + "pyiron_potentialfit.spgfit.structures", "pyiron_potentialfit.assyst.structures" ) add_module_conversion( - "pyiron_potentialfit.spgfit.projectflow", - "pyiron_potentialfit.assyst.projectflow" + "pyiron_potentialfit.spgfit.projectflow", "pyiron_potentialfit.assyst.projectflow" ) from ._version import get_versions diff --git a/pyiron_potentialfit/assyst/calculations/__init__.py b/pyiron_potentialfit/assyst/calculations/__init__.py index e454dcec..d58dbb53 100644 --- a/pyiron_potentialfit/assyst/calculations/__init__.py +++ b/pyiron_potentialfit/assyst/calculations/__init__.py @@ -1,8 +1,8 @@ from .workflow import ( - CalculationConfig, - ServerConfig, - VaspConfig, - WorkflowProjectConfig, - run + CalculationConfig, + ServerConfig, + VaspConfig, + WorkflowProjectConfig, + run, ) from ..projectflow import RunAgain diff --git a/pyiron_potentialfit/assyst/calculations/workflow.py b/pyiron_potentialfit/assyst/calculations/workflow.py index 1bd08176..59d197db 100644 --- a/pyiron_potentialfit/assyst/calculations/workflow.py +++ b/pyiron_potentialfit/assyst/calculations/workflow.py @@ -72,6 +72,7 @@ def collect( train.run() return train + @dataclass class CalculationConfig: vasp: VaspConfig @@ -127,13 +128,17 @@ def if_new(train): if train.input.read_only: train.input.unlock() if config.vasp.magmoms is not None and len(config.vasp.magmoms) > 0: + def apply_magmom(structure): - if not structure.has('initial_magmoms'): + if not structure.has("initial_magmoms"): structure.set_initial_magnetic_moments( - [config.vasp.magmoms.get(sym, 0.0) for sym in structure.symbols] + [config.vasp.magmoms.get(sym, 0.0) for sym in structure.symbols] ) return structure - filtered_cont = filtered_cont.transform_structures(apply_magmom).collect_structures() + + filtered_cont = filtered_cont.transform_structures( + apply_magmom + ).collect_structures() train.input.structures = filtered_cont train.input.job = config.get_job() @@ -149,7 +154,10 @@ def if_finished(train): return results with warnings.catch_warnings(): - warnings.filterwarnings("ignore", message="'KSPACING' found in INCAR, no KPOINTS file written",) + warnings.filterwarnings( + "ignore", + message="'KSPACING' found in INCAR, no KPOINTS file written", + ) return train.check( config.workflow, if_new, @@ -157,7 +165,14 @@ def if_finished(train): number_of_jobs=train.input.structures.number_of_structures, ) -def run(pr: Project, config: CalculationConfig, *containers: "StructureContainer", tries: int = 10, wait: float = 60): + +def run( + pr: Project, + config: CalculationConfig, + *containers: "StructureContainer", + tries: int = 10, + wait: float = 60 +): """ Run high quality DFT on all structures in `containers`. @@ -193,7 +208,9 @@ def run(pr: Project, config: CalculationConfig, *containers: "StructureContainer if i + 1 < tries: time.sleep(wait) if retry: - warnings.warn("Structure creation is not finished! Call this function again later!") + warnings.warn( + "Structure creation is not finished! Call this function again later!" + ) def deduplicate(cont, replace=True): @@ -266,7 +283,7 @@ def combine( df = cont.to_pandas() df["name"] = df.name.map(lambda s: cont.name + "_" + s) if energy_cap is not None: - I = df.energy/df.number_of_atoms <= energy_cap + I = df.energy / df.number_of_atoms <= energy_cap df = df.loc[I] if force_cap is not None: I = df.forces.map(lambda f: np.linalg.norm(f, axis=-1).max() < force_cap) diff --git a/pyiron_potentialfit/assyst/structures/__init__.py b/pyiron_potentialfit/assyst/structures/__init__.py index 9aa78188..76792d6d 100644 --- a/pyiron_potentialfit/assyst/structures/__init__.py +++ b/pyiron_potentialfit/assyst/structures/__init__.py @@ -1,7 +1,7 @@ from .workflow import ( - TrainingDataConfig, - ServerConfig, - VaspConfig, - WorkflowProjectConfig, - run, + TrainingDataConfig, + ServerConfig, + VaspConfig, + WorkflowProjectConfig, + run, ) diff --git a/pyiron_potentialfit/assyst/structures/job.py b/pyiron_potentialfit/assyst/structures/job.py index 562bb4b8..37f5b064 100644 --- a/pyiron_potentialfit/assyst/structures/job.py +++ b/pyiron_potentialfit/assyst/structures/job.py @@ -4,16 +4,15 @@ from .workflow import export_structures, run, TrainingDataConfig + class AssystStructures(PythonTemplateJob): """ Create structure set with ASSYST. """ + def __init__(self, project, job_name): super().__init__(project, job_name) - self.input.update(asdict(TrainingDataConfig( - elements=["Mg"], - name=None - ))) + self.input.update(asdict(TrainingDataConfig(elements=["Mg"], name=None))) @property def child_project(self): @@ -26,7 +25,9 @@ def run_static(self): self.status.running = True run(self.child_project, TrainingDataConfig(**self.input), tries=None) self.status.collect = True - for cont in self.child_project["containers"].iter_jobs(hamilton="StructureContainer"): + for cont in self.child_project["containers"].iter_jobs( + hamilton="StructureContainer" + ): # proper but loses identifiers # self.output[cont.name] = cont.collect_structures() self.output[cont.name] = cont._container.copy() diff --git a/pyiron_potentialfit/assyst/structures/minimize.py b/pyiron_potentialfit/assyst/structures/minimize.py index 4e97e1f3..d407f2a8 100644 --- a/pyiron_potentialfit/assyst/structures/minimize.py +++ b/pyiron_potentialfit/assyst/structures/minimize.py @@ -13,8 +13,8 @@ ) from pyiron_potentialfit.atomistics.job.trainingcontainer import ( - TrainingContainer, - TrainingStorage, + TrainingContainer, + TrainingStorage, ) from pyiron_atomistics.atomistics.structure.structurestorage import StructureStorage from pyiron_atomistics.atomistics.job.structurecontainer import StructureContainer @@ -22,6 +22,7 @@ from traitlets import Instance, Float, Bool, Int, CaselessStrEnum, Dict + class MinimizeVaspInput(Input): symlink = Bool(default_value=False, help="Whether to symlink the project or not") @@ -45,10 +46,12 @@ class MinimizeVaspInput(Input): min_dist = Float(default_value=None, allow_none=True) accept_not_converged = Int(default_value=False) + class MinimizeOutput(Output): final_structures = Instance(TrainingStorage, args=()) trace_structures = Instance(TrainingStorage, args=()) + class MinimizeVaspFlow(ProjectFlow): Input = MinimizeVaspInput @@ -80,13 +83,17 @@ def _run(self, delete_existing_job=False, delete_aborted_job=True): sflow.input.job = vasp if vasp_config.magmoms is not None and len(vasp_config.magmoms) > 0: + def apply_magmom(structure): - if not structure.has('initial_magmoms'): + if not structure.has("initial_magmoms"): structure.set_initial_magnetic_moments( - [vasp_config.magmoms.get(sym, 0.0) for sym in structure.symbols] + [vasp_config.magmoms.get(sym, 0.0) for sym in structure.symbols] ) return structure - sflow.input.structures = self.input.structures.transform_structures(apply_magmom).collect_structures() + + sflow.input.structures = self.input.structures.transform_structures( + apply_magmom + ).collect_structures() else: sflow.input.structures = self.input.structures.copy() sflow.input.table_setup = lambda tab: tab @@ -94,21 +101,29 @@ def apply_magmom(structure): sflow.attach(self.project, "structures").run() def _analyze(self, delete_existing_job=False): - if self.output.final_structures.number_of_structures > 0 and not delete_existing_job: + if ( + self.output.final_structures.number_of_structures > 0 + and not delete_existing_job + ): return ok_status = ["finished"] if self.input.accept_not_converged: ok_status += ["not_converged", "warning"] for j in self.project.iter_jobs(hamilton="Vasp", convert_to_object=False): - if j.status not in ok_status: continue + if j.status not in ok_status: + continue N = len(j["output/generic/steps"]) stride = max(N // self.input.number_of_structures, 1) - for i in range(1, N + 1, stride)[:self.input.number_of_structures]: + for i in range(1, N + 1, stride)[: self.input.number_of_structures]: s = self._extract_structure(j, -i) if ( self.input.min_dist is not None - and np.prod(s.get_neighbors(cutoff_radius=self.input.min_dist).distances.shape) + and np.prod( + s.get_neighbors( + cutoff_radius=self.input.min_dist + ).distances.shape + ) > 0 ): continue @@ -116,9 +131,13 @@ def _analyze(self, delete_existing_job=False): name = j.name else: name = f"{j.name}_step_{i}" - self.output.trace_structures.include_job(j, iteration_step=-i, identifier=name) + self.output.trace_structures.include_job( + j, iteration_step=-i, identifier=name + ) if i == 1: - self.output.final_structures.include_job(j, iteration_step=-i, identifier=name) + self.output.final_structures.include_job( + j, iteration_step=-i, identifier=name + ) @staticmethod def _extract_structure(jobpath, frame): @@ -144,6 +163,7 @@ def _extract_structure(jobpath, frame): ) return structure + def minimize( pr, cont: StructureContainer, @@ -185,7 +205,9 @@ def if_finished(flow): if cont is None: cont = flow.project.create.job.StructureContainer("Trace") for i, s in enumerate(flow.output.trace_structures.iter_structures()): - cont.add_structure(s, identifier=flow.output.trace_structures["identifier", i]) + cont.add_structure( + s, identifier=flow.output.trace_structures["identifier", i] + ) cont.run() cont.copy_to( pr.create_group("containers"), @@ -195,10 +217,13 @@ def if_finished(flow): if cont is None: cont = flow.project.create.job.StructureContainer("Final") for i, s in enumerate(flow.output.final_structures.iter_structures()): - cont.add_structure(s, identifier=flow.output.final_structures["identifier", i]) + cont.add_structure( + s, identifier=flow.output.final_structures["identifier", i] + ) cont.run() cont.copy_to(pr["containers"], new_job_name=flow.project.name) return cont - return minf.check(workflow, if_new, if_finished, - number_of_jobs=cont.number_of_structures) + return minf.check( + workflow, if_new, if_finished, number_of_jobs=cont.number_of_structures + ) diff --git a/pyiron_potentialfit/assyst/structures/random.py b/pyiron_potentialfit/assyst/structures/random.py index ba0fb32a..497b00ba 100644 --- a/pyiron_potentialfit/assyst/structures/random.py +++ b/pyiron_potentialfit/assyst/structures/random.py @@ -8,6 +8,7 @@ import numpy as np from tqdm.auto import tqdm + def shake(displacement=0.1): """ Return a function that randomly displaces atoms in structures. @@ -74,7 +75,7 @@ def fill_container( # one weird aspect ratios, the neighbor searching code can allocate huge structures, # because it explicitly repeats the structure to create ghost atoms # since we only care about the presence of short distances between atoms and not the - # real neighbor information, simply double the structure to make sure we see all bonds + # real neighbor information, simply double the structure to make sure we see all bonds # and turn off PBC sd = s.repeat(2) sd.pbc = [False, False, False] @@ -117,10 +118,7 @@ def rattle( rand, repetitions=rattle_repetitions, combine=2, - modifiers=( - (1, shake(rattle_disp)), - (1, stretch(rattle_strain)) - ), + modifiers=((1, shake(rattle_disp)), (1, stretch(rattle_strain))), min_dist=min_dist, ) logger.info("added %i rattle structures", rand.number_of_structures - N) diff --git a/pyiron_potentialfit/assyst/structures/spg.py b/pyiron_potentialfit/assyst/structures/spg.py index 4f8cc918..ca54d3df 100644 --- a/pyiron_potentialfit/assyst/structures/spg.py +++ b/pyiron_potentialfit/assyst/structures/spg.py @@ -13,6 +13,7 @@ from ..util import RCORE, DistanceFilter + def _pyxtal( group: Union[int, List[int]], species: Tuple[str], @@ -179,7 +180,7 @@ def check_cell_shape(structure): if min_dist is None: distance_filter = DistanceFilter() else: - distance_filter = DistanceFilter({e: min_dist/2 for e in elements}) + distance_filter = DistanceFilter({e: min_dist / 2 for e in elements}) el, ni = zip(*((el, ni) for el, ni in zip(elements, num_ions) if ni > 0)) # missing checker support # pr.create.structure.pyxtal( diff --git a/pyiron_potentialfit/assyst/structures/workflow.py b/pyiron_potentialfit/assyst/structures/workflow.py index 175b1ae8..e9e84278 100644 --- a/pyiron_potentialfit/assyst/structures/workflow.py +++ b/pyiron_potentialfit/assyst/structures/workflow.py @@ -18,6 +18,7 @@ from pyiron_base import Project + class State(Enum): """ The current state of the structure generation. @@ -61,13 +62,20 @@ def __post_init__(self): # deprecated; use workflow config delete_existing_job: bool = False - vasp: VaspConfig = field(default_factory=lambda: VaspConfig(encut=None, kmesh=Kspacing(0.5))) - server: ServerConfig = field(default_factory=lambda: ServerConfig(cores=10, run_time=5*60, queue='cmti')) - workflow: WorkflowProjectConfig = field(default_factory=lambda: WorkflowProjectConfig( - delete_existing_job=False, - broken_threshold=0.1, - finished_threshold=0.9, - )) + vasp: VaspConfig = field( + default_factory=lambda: VaspConfig(encut=None, kmesh=Kspacing(0.5)) + ) + server: ServerConfig = field( + default_factory=lambda: ServerConfig(cores=10, run_time=5 * 60, queue="cmti") + ) + workflow: WorkflowProjectConfig = field( + default_factory=lambda: WorkflowProjectConfig( + delete_existing_job=False, + broken_threshold=0.1, + finished_threshold=0.9, + ) + ) + def create_structure_set( pr: Project, @@ -159,7 +167,10 @@ def create_structure_set( state = State.FINISHED return state -def run(pr: Project, config: TrainingDataConfig, tries: Optional[int] = 10, wait: int = 60): + +def run( + pr: Project, config: TrainingDataConfig, tries: Optional[int] = 10, wait: int = 60 +): """ Create structure set. @@ -182,7 +193,10 @@ def run(pr: Project, config: TrainingDataConfig, tries: Optional[int] = 10, wait counter = range(tries) for i in counter: with warnings.catch_warnings(): - warnings.filterwarnings("ignore", message="'KSPACING' found in INCAR, no KPOINTS file written",) + warnings.filterwarnings( + "ignore", + message="'KSPACING' found in INCAR, no KPOINTS file written", + ) state = create_structure_set(pr, state, config, fast_forward=True) pr.data.state = state.value pr.data.write() @@ -191,13 +205,19 @@ def run(pr: Project, config: TrainingDataConfig, tries: Optional[int] = 10, wait if i + 1 < tries: time.sleep(wait) if state != State.FINISHED: - warnings.warn("Structure creation is not finished! Call this function again later!") + warnings.warn( + "Structure creation is not finished! Call this function again later!" + ) return state.value + def export_structures(pr, export, ending, format): os.makedirs(export, exist_ok=True) for cont in pr["containers"].iter_jobs(hamilton="StructureContainer"): dir_path = os.path.join(export, cont.name) os.makedirs(dir_path, exist_ok=True) for i, s in enumerate(cont.iter_structures()): - s.write(os.path.join(dir_path, cont._container["identifier", i]) + "." + ending, format=format) + s.write( + os.path.join(dir_path, cont._container["identifier", i]) + "." + ending, + format=format, + ) diff --git a/pyiron_potentialfit/assyst/util.py b/pyiron_potentialfit/assyst/util.py index 3e776d06..2541063a 100644 --- a/pyiron_potentialfit/assyst/util.py +++ b/pyiron_potentialfit/assyst/util.py @@ -126,12 +126,13 @@ def get_table(pr, table_name, add=None, delete_existing_job=False): raise pr.remove_job(table_name) if add is None: - raise ValueError('add cannot be None on first run!') + raise ValueError("add cannot be None on first run!") tab = pr.create_table(table_name, delete_existing_job=delete_existing_job) add(tab) tab.run() return tab + def read_generic_parameters(hdf, key): gp = GenericParameters(table_name="data_dict") gp.from_hdf(hdf) @@ -176,6 +177,7 @@ def symlink_project(pr: Project): # Generic config objects for all tools + @dataclass class ServerConfig: """Stores computational parameters and applies them to pyiron jobs.""" diff --git a/pyiron_potentialfit/assyst/vasp.py b/pyiron_potentialfit/assyst/vasp.py index 6f9dcda5..31cf9607 100644 --- a/pyiron_potentialfit/assyst/vasp.py +++ b/pyiron_potentialfit/assyst/vasp.py @@ -1,33 +1,40 @@ """Helper classes to run vasp jobs.""" + import abc from dataclasses import dataclass, field from typing import Union, Optional, Dict, Tuple from pyiron_base import GenericJob + @dataclass class KMeshSpec(abc.ABC): @abc.abstractmethod def configure(self, job): pass + @dataclass(slots=True) class Kpoints(KMeshSpec): - kpoints: Union[int,Tuple[int, int, int]] + kpoints: Union[int, Tuple[int, int, int]] + def configure(self, job): job.set_kpoints(self.kpoints) + @dataclass(slots=True) class Kspacing(KMeshSpec): kspacing: float + def configure(self, job): try: - job.input.incar['KSPACING'] = self.kspacing + job.input.incar["KSPACING"] = self.kspacing except AttributeError: # called on a VaspFactory instead of a Vasp job - job.incar['KSPACING'] = self.kspacing + job.incar["KSPACING"] = self.kspacing # job.set_kpoints(k_mesh_spacing=self.kspacing) + @dataclass class VaspConfig: encut: Optional[float] = None @@ -61,8 +68,8 @@ def configure_vasp_job(self, job): self.kmesh.configure(job) for element, path in self.potcars.items(): job.potential[element] = path - if self.magmoms is not None and 'LORBIT' not in self.incar: - self.incar['LORBIT'] = 10 + if self.magmoms is not None and "LORBIT" not in self.incar: + self.incar["LORBIT"] = 10 for k, v in self.incar.items(): try: job.input.incar[k] = v diff --git a/pyiron_potentialfit/ml/potentialfit.py b/pyiron_potentialfit/ml/potentialfit.py index 1d6cc612..90ba8254 100644 --- a/pyiron_potentialfit/ml/potentialfit.py +++ b/pyiron_potentialfit/ml/potentialfit.py @@ -102,6 +102,7 @@ def get_lammps_potential(self) -> pd.DataFrame: """ pass + def _scatter(x, y): """ Adaptive scatter plot. @@ -112,11 +113,12 @@ def _scatter(x, y): if len(x) < 100: plt.scatter(x, y) elif len(x) < 1000: - plt.scatter(x, y, marker='.') + plt.scatter(x, y, marker=".") else: - plt.hexbin(x, y, bins='log') + plt.hexbin(x, y, bins="log") plt.colorbar(label="Observations") + def _annotated_vline(x, text, trafo, linestyle="--"): plt.axvline(x, color="k", linestyle=linestyle) plt.text( @@ -129,6 +131,7 @@ def _annotated_vline(x, text, trafo, linestyle="--"): path_effects=[withStroke(linewidth=4, foreground="w")], ) + class PotentialPlots: def __init__(self, training_data, predicted_data): self._training_data = training_data @@ -251,9 +254,7 @@ def force_log_histogram( ax = plt.gca() trafo = ax.get_xaxis_transform() - plt.hist( - df, bins=np.logspace(np.log10(low), np.log10(high), bins), log=logy - ) + plt.hist(df, bins=np.logspace(np.log10(low), np.log10(high), bins), log=logy) plt.xscale("log") _annotated_vline(rmse, f"RMSE = {rmse:.02}", trafo) _annotated_vline(mae, f"MAE = {mae:.02}", trafo) diff --git a/pyiron_potentialfit/spgfit/calculations.py b/pyiron_potentialfit/spgfit/calculations.py index 710d8c4c..4ce25149 100644 --- a/pyiron_potentialfit/spgfit/calculations.py +++ b/pyiron_potentialfit/spgfit/calculations.py @@ -5,11 +5,7 @@ from pyiron_base import Project from ..assyst.projectflow import RunAgain -from ..assyst.calculations.workflow import ( - CalculationConfig, - run_container, - combine -) +from ..assyst.calculations.workflow import CalculationConfig, run_container, combine from ..assyst.util import fast_forward, ServerConfig from ..assyst.vasp import VaspConfig @@ -108,9 +104,7 @@ def main(): server_group = parser.add_argument_group("Server") server_group.add_argument( - "--queue", - default=ServerConfig.queue, - help="Pyiron queue to submit jobs to" + "--queue", default=ServerConfig.queue, help="Pyiron queue to submit jobs to" ) server_group.add_argument( "--cores", @@ -155,21 +149,21 @@ def main(): } conf = CalculationConfig( - vasp=VaspConfig( - encut=args.encut, - kmesh=args.kpoints, - incar=incar, - ), - server=ServerConfig( - cores=args.cores, - run_time=args.run_time, - ), - workflow=WorkflowProjectConfig( - delete_existing_job=args.delete_existing_job, - broken_threshold=args.broken_threshold, - finished_threshold=0.9, - ), - min_dist=args.min_dist, + vasp=VaspConfig( + encut=args.encut, + kmesh=args.kpoints, + incar=incar, + ), + server=ServerConfig( + cores=args.cores, + run_time=args.run_time, + ), + workflow=WorkflowProjectConfig( + delete_existing_job=args.delete_existing_job, + broken_threshold=args.broken_threshold, + finished_threshold=0.9, + ), + min_dist=args.min_dist, ) pr = Project(args.project) diff --git a/pyiron_potentialfit/spgfit/learn.py b/pyiron_potentialfit/spgfit/learn.py index 610ba9e9..551245ce 100644 --- a/pyiron_potentialfit/spgfit/learn.py +++ b/pyiron_potentialfit/spgfit/learn.py @@ -102,7 +102,7 @@ def fit( training_containers = tuple(training_containers) train_name = "_".join(train.name for train in training_containers) train_number_of_structures = sum( - train.number_of_structures for train in training_containers + train.number_of_structures for train in training_containers ) pr = fit_pr.create_group(train_name) @@ -264,7 +264,7 @@ def energy_mae(j): N = inpt.get_array("length") train = np.squeeze(inpt.get_array("energy")) / N pred = np.squeeze(j["output/training_efs"].to_object().get_array("energy")) / N - return np.abs(train-pred).mean() + return np.abs(train - pred).mean() def energy_max(j): diff --git a/pyiron_potentialfit/spgfit/structures.py b/pyiron_potentialfit/spgfit/structures.py index d9dfeb59..15886f90 100644 --- a/pyiron_potentialfit/spgfit/structures.py +++ b/pyiron_potentialfit/spgfit/structures.py @@ -23,13 +23,13 @@ from ..assyst.vasp import Kpoints, Kspacing, VaspConfig from ..assyst.structures.workflow import ( - export_structures, - TrainingDataConfig, State, - create_structure_set + export_structures, + TrainingDataConfig, + State, + create_structure_set, ) - def run(pr: Project, config: TrainingDataConfig, wait_time=60): """ Create a new structure set. @@ -42,9 +42,12 @@ def run(pr: Project, config: TrainingDataConfig, wait_time=60): wait_time (int): how many seconds to sleep between calling :func:`.create_structure_set`. """ state = State.SPG - while (state := create_structure_set(pr, state, conf, fast_forward=True)) != State.FINISHED: + while ( + state := create_structure_set(pr, state, conf, fast_forward=True) + ) != State.FINISHED: time.sleep(wait_time) + def run(pr: Project, config: TrainingDataConfig, wait_time=60): """ Create a new structure set. @@ -57,9 +60,12 @@ def run(pr: Project, config: TrainingDataConfig, wait_time=60): wait_time (int): how many seconds to sleep between calling :func:`.create_structure_set`. """ state = State.SPG - while (state := create_structure_set(pr, state, conf, fast_forward=True)) != State.FINISHED: + while ( + state := create_structure_set(pr, state, conf, fast_forward=True) + ) != State.FINISHED: time.sleep(wait_time) + epilog = """ Follows the systematic approach reported in this paper[1]. Execution is organized in four steps: @@ -209,25 +215,35 @@ def main(): help="Retry the current step from scratch", ) parser.add_argument( - "--export", type=str, + "--export", + type=str, help="Optionally specify a directory where to dump POSCAR files with the generated structures after everything " - "is finished." + "is finished.", ) parser.add_argument( - "--magmom", nargs=2, action="append", default=[], + "--magmom", + nargs=2, + action="append", + default=[], help="Initial magnetic moments as `element symbol`, followed by the collinear magnetic moment; all atoms of " - "the same element will be initialized the same; may be given multiple times, once per element" + "the same element will be initialized the same; may be given multiple times, once per element", ) parser.add_argument( - "--potcar", nargs=2, action="append", default=[], type=dict, + "--potcar", + nargs=2, + action="append", + default=[], + type=dict, help="use these POTCARs instead of the default ones; must be given as " - "TYPE PATH pairs, but may be given multiple times, one for each " - "element." + "TYPE PATH pairs, but may be given multiple times, one for each " + "element.", ) parser.add_argument( - "--cores", type=int, default=10, - help="Number of cores to use per job during minimizations" + "--cores", + type=int, + default=10, + help="Number of cores to use per job during minimizations", ) args = parser.parse_args() @@ -245,10 +261,12 @@ def main(): conf = {k: v for k, v in conf.items() if v is not None} conf = TrainingDataConfig(**conf) - for (el, mm) in args.magmom: + for el, mm in args.magmom: mm = float(mm) if el not in args.elements: - raise ValueError(f"Elements for magmoms, must be also given via -e, not {el}!") + raise ValueError( + f"Elements for magmoms, must be also given via -e, not {el}!" + ) conf.vasp.magmoms[el] = mm if args.potcar is not {}: conf.vasp.potcars = args.potcar diff --git a/setup.py b/setup.py index 8682aa60..b901239d 100644 --- a/setup.py +++ b/setup.py @@ -31,16 +31,15 @@ packages=find_packages(exclude=["*tests*"]), install_requires=[ 'ase==3.23.0', - 'pyiron_atomistics==0.6.9', 'matplotlib>=3.8.4,<4', 'numpy==1.26.4', - 'pyiron_base==0.9.10', + 'pyiron_base==0.9.11', + 'pyiron_atomistics==0.6.10', 'scipy==1.14.0', 'runnerase==0.3.3', 'pyiron_snippets>=0.1.1,<=0.1.4', 'pyparsing>=3.1.2,<3.2', # spgfit - 'pyiron_atomistics==0.6.4', 'pyiron_contrib==0.1.18', 'dill>=0.3.0', 'seaborn>=0.13.0,<0.14',