diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 8a6df6fe3..f20556dc0 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -5,15 +5,15 @@ dependencies: - coveralls - coverage - codacy-coverage -- matplotlib =3.5.1 -- numpy =1.22.2 -- pyiron_base =0.5.5 -- pyiron_atomistics =0.2.37 -- pyparsing =3.0.7 -- scipy =1.8.0 -- seaborn =0.11.2 -- scikit-image =0.19.2 +- matplotlib =3.6.1 +- numpy =1.23.4 +- pyiron_base =0.5.26 +- pyiron_atomistics =0.2.58 +- pyparsing =3.0.9 +- scipy =1.9.3 +- seaborn =0.12.0 +- scikit-image =0.19.3 - randspg =0.0.1 +- boto3 =1.24.96 +- moto =4.0.8 - pycp2k =0.2.2 -- boto3 =1.21.3 -- moto =3.0.4 diff --git a/.github/delete-merged-branch-config.yml b/.github/delete-merged-branch-config.yml index 8a1c49b7e..76898c70c 100644 --- a/.github/delete-merged-branch-config.yml +++ b/.github/delete-merged-branch-config.yml @@ -1,3 +1,3 @@ exclude: - - master + - main delete_closed_pr: false diff --git a/.github/workflows/UpdateDependabotPR.yml b/.github/workflows/UpdateDependabotPR.yml index 606ab2bed..b3971e999 100644 --- a/.github/workflows/UpdateDependabotPR.yml +++ b/.github/workflows/UpdateDependabotPR.yml @@ -2,7 +2,7 @@ name: UpdateDependabotPR on: pull_request_target: - branches: [ master ] + branches: [ main ] jobs: build: diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 5c7f471b0..3dbc0395c 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -5,9 +5,9 @@ name: Coverage on: push: - branches: [ master ] + branches: [ main ] pull_request: - branches: [ master ] + branches: [ main ] jobs: build: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 000000000..534ee33ec --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,37 @@ +# This workflow is used to test, if the documentation can build + +name: Docs + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: conda-incubator/setup-miniconda@v2 + with: + python-version: "3.10" + mamba-version: "*" + channels: conda-forge + channel-priority: strict + auto-update-conda: true + environment-file: .ci_support/environment.yml + - name: Setup + shell: bash -l {0} + run: | + python .ci_support/pyironconfig.py + pip install --no-deps . + conda env update --name test --file docs/environment.yml + - name: Documentation + shell: bash -l {0} + run: | + mkdir public_html; cd docs + sphinx-build -b html ./ ../public_html || exit 1; + cd .. diff --git a/.github/workflows/notebooks.yml b/.github/workflows/notebooks.yml index 124fb5653..45ea78586 100644 --- a/.github/workflows/notebooks.yml +++ b/.github/workflows/notebooks.yml @@ -2,30 +2,20 @@ name: Notebooks on: push: - branches: [ master ] + branches: [ main ] pull_request: - branches: [ master ] + branches: [ main ] jobs: - build: - + build-notebooks: + needs: commit-updated-env runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: conda-incubator/setup-miniconda@v2 - with: - python-version: "3.10" - mamba-version: "*" - channels: conda-forge - channel-priority: strict - auto-update-conda: true - environment-file: .ci_support/environment.yml - - name: Setup - shell: bash -l {0} - run: | - pip install --no-deps . - conda install papermill - - name: Tests - shell: bash -l {0} - run: ./.ci_support/build_notebooks.sh + - uses: actions/checkout@v3 + - uses: pyiron/actions/build-notebooks@main + with: + python-version: '3.10' + env-prefix: /usr/share/miniconda3/envs/my-env + env-label: linux-64-py-3-10 + env-files: .ci_support/environment.yml + exclusion-file: .ci_support/exclude \ No newline at end of file diff --git a/.github/workflows/pypicheck.yml b/.github/workflows/pypicheck.yml index 85a74694f..01ff4ee6c 100644 --- a/.github/workflows/pypicheck.yml +++ b/.github/workflows/pypicheck.yml @@ -2,9 +2,9 @@ name: Pip check on: push: - branches: [ master ] + branches: [ main ] pull_request: - branches: [ master ] + branches: [ main ] jobs: build: diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml index fae31731a..25cb8c5c1 100644 --- a/.github/workflows/unittests.yml +++ b/.github/workflows/unittests.yml @@ -1,35 +1,43 @@ # This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions -name: Python package - +name: Unit Tests on: push: - branches: [ master ] + branches: [ main ] pull_request: - branches: [ master ] + branches: [ main ] jobs: build: - env: - CONDA_PREFIX: /usr/share/miniconda/ runs-on: ${{ matrix.operating-system }} strategy: matrix: - operating-system: [ubuntu-latest] - python-version: ["3.8", "3.9", "3.10"] + include: + - operating-system: ubuntu-latest + python-version: '3.10' + label: linux-64-py-3-10 + prefix: /usr/share/miniconda3/envs/my-env + + - operating-system: ubuntu-latest + python-version: 3.9 + label: linux-64-py-3-9 + prefix: /usr/share/miniconda3/envs/my-env + + - operating-system: ubuntu-latest + python-version: 3.8 + label: linux-64-py-3-8 + prefix: /usr/share/miniconda3/envs/my-env steps: - uses: actions/checkout@v2 - - uses: conda-incubator/setup-miniconda@v2 + - uses: pyiron/actions/cached-mamba@main with: python-version: ${{ matrix.python-version }} - mamba-version: "*" - channels: conda-forge - channel-priority: strict - auto-update-conda: true - environment-file: .ci_support/environment.yml + env-prefix: ${{ matrix.prefix }} + env-label: ${{ matrix.label }} + env-files: .ci_support/environment.yml - name: Setup shell: bash -l {0} run: | diff --git a/.readthedocs.yml b/.readthedocs.yml index 5cd4f83c6..cf20a64f1 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -5,20 +5,21 @@ # Required version: 2 +build: + os: "ubuntu-20.04" + tools: + python: "mambaforge-4.10" + # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py -# Optionally build your docs in additional formats such as PDF and ePub -formats: [] - # Install pyiron from conda conda: environment: docs/environment.yml # Optionally set the version of Python and requirements required to build your docs python: - version: 3.7 install: - method: pip path: . diff --git a/pyiron_contrib/RDM/storagejob.py b/pyiron_contrib/RDM/storagejob.py index e9b40c8e8..c10a7bd81 100644 --- a/pyiron_contrib/RDM/storagejob.py +++ b/pyiron_contrib/RDM/storagejob.py @@ -3,7 +3,7 @@ import shutil from pyiron_base import GenericJob, DataContainer -from pyiron_base.generic.filedata import FileData +from pyiron_base import FileData from pyiron_contrib.generic.s3io import FileS3IO diff --git a/pyiron_contrib/__init__.py b/pyiron_contrib/__init__.py index 66b2f59f1..3a43350a1 100644 --- a/pyiron_contrib/__init__.py +++ b/pyiron_contrib/__init__.py @@ -44,6 +44,8 @@ JOB_CLASS_DICT['Atomicrex'] = 'pyiron_contrib.atomistics.atomicrex.atomicrex_job' JOB_CLASS_DICT['StructureMasterInt'] = 'pyiron_contrib.atomistics.atomistics.job.structurelistmasterinteractive' JOB_CLASS_DICT['StorageJob'] = 'pyiron_contrib.RDM.storagejob' +JOB_CLASS_DICT['PacemakerJob'] = 'pyiron_contrib.atomistics.pacemaker.job' +JOB_CLASS_DICT['MeamFit'] = 'pyiron_contrib.atomistics.meamfit.meamfit' JOB_CLASS_DICT['Cp2kJob'] = 'pyiron_contrib.atomistics.cp2k.job' diff --git a/pyiron_contrib/atomistics/atomicrex/base.py b/pyiron_contrib/atomistics/atomicrex/base.py index e147fd8c4..ef973cd5a 100644 --- a/pyiron_contrib/atomistics/atomicrex/base.py +++ b/pyiron_contrib/atomistics/atomicrex/base.py @@ -4,8 +4,9 @@ """Pyiron interface to atomicrex""" import numpy as np +import pandas as pd -from pyiron_base import state, GenericJob, Executable +from pyiron_base import state, GenericJob, Executable, FlattenedStorage from pyiron_contrib.atomistics.atomicrex.general_input import ( GeneralARInput, @@ -15,16 +16,14 @@ from pyiron_contrib.atomistics.atomicrex.potential_factory import ARPotFactory from pyiron_contrib.atomistics.atomicrex.output import Output from pyiron_contrib.atomistics.atomicrex.function_factory import FunctionFactory +from pyiron_contrib.atomistics.ml.potentialfit import PotentialFit +from pyiron_contrib.atomistics.atomistics.job.trainingcontainer import ( + TrainingContainer, + TrainingStorage, +) -## Class defined for future addition of other codes -## Not sure which functionality (if any) can be extracted yet, but a similar pattern is followed in other pyiron modules -class PotentialFittingBase(GenericJob): - def __init__(self, project, job_name): - super().__init__(project, job_name) - - -class AtomicrexBase(PotentialFittingBase): +class AtomicrexBase(GenericJob, PotentialFit): __version__ = "0.1.0" __hdf_version__ = "0.1.0" """Class to set up and run atomicrex jobs""" @@ -217,7 +216,7 @@ def write_input(self, directory=None): """ if directory is None: directory = self.working_directory - self.input._write_xml_file(directory=directory) + self.input._write_xml_file(directory=directory, job=self) self.potential.write_xml_file(directory=directory) self.structures.write_xml_file(directory=directory) @@ -248,8 +247,7 @@ def _executable_activate(self, enforce=False): # instead of the potential_as_pd_df function @property def lammps_potential(self): - pot = self.potential_as_pd_df() - return pot + return self.potential_as_pd_df() def potential_as_pd_df(self): """ @@ -258,6 +256,26 @@ def potential_as_pd_df(self): """ return self.potential._potential_as_pd_df(job=self) + #### PotentialFit methods + def _add_training_data(self, container: TrainingContainer) -> None: + self.structures.add_training_data(container) + + def _get_training_data(self) -> TrainingStorage: + return self.structures.get_training_data() + + def _get_predicted_data(self) -> FlattenedStorage: + return self.structures.get_predicted_data() + + def get_lammps_potential(self) -> pd.DataFrame: + """ + Return a pyiron compatible dataframe that defines a potential to be used with a Lammps job (or subclass + thereof). + + Returns: + DataFrame: contains potential information to be used with a Lammps job. + """ + return self.potential_as_pd_df() + class Factories: """ diff --git a/pyiron_contrib/atomistics/atomicrex/fit_properties.py b/pyiron_contrib/atomistics/atomicrex/fit_properties.py index a5426d38d..5575253ae 100644 --- a/pyiron_contrib/atomistics/atomicrex/fit_properties.py +++ b/pyiron_contrib/atomistics/atomicrex/fit_properties.py @@ -240,7 +240,7 @@ def __init__(self, num_chunks=1, num_elements=1, **kwargs): super().__init__(num_chunks=num_chunks, num_elements=num_elements, **kwargs) self._per_chunk_arrays = {} self.add_array("fit", dtype=bool, per="chunk", fill=False) - self.add_array("relative_weight", per="chunk", fill=np.nan) + self.add_array("relative_weight", per="chunk", fill=1.0) self.add_array("relax", dtype=bool, per="chunk") self.add_array("residual_style", per="chunk", dtype=np.ubyte, fill=0) self.add_array("output", dtype=bool, per="chunk", fill=False) diff --git a/pyiron_contrib/atomistics/atomicrex/function_factory.py b/pyiron_contrib/atomistics/atomicrex/function_factory.py index eef189dbf..852b6b92c 100644 --- a/pyiron_contrib/atomistics/atomicrex/function_factory.py +++ b/pyiron_contrib/atomistics/atomicrex/function_factory.py @@ -166,7 +166,7 @@ def gaussian(identifier, prefactor, eta, mu, species=["*", "*"], cutoff=None): @staticmethod def x_pow_n_cutoff( - identifier, cutoff, h=1, N=4, species=["*"], is_screening_function=True + identifier, cutoff, h=1, N=4, species=["*", "*"], is_screening_function=True ): return XpowNCutoff( identifier=identifier, @@ -244,12 +244,34 @@ def MishinCuRho(identifier, a, r1, r2, beta1, beta2, species=["*", "*"]): return MishinCuRho(identifier, a, r1, r2, beta1, beta2, species) @staticmethod - def MishinCuF(identifier, F0, F2, q1, q2, q3, q4, Q1, Q2, species=["*", "*"]): + def MishinCuF(identifier, F0, F2, q1, q2, q3, q4, Q1, Q2, species=["*"]): return MishinCuF(identifier, F0, F2, q1, q2, q3, q4, Q1, Q2, species) @staticmethod - def RsMinusRPowN(identifier, S, rs, N, species=["*", "*"]): - return RsMinusRPowN(identifier, S, rs, N, species) + def extendedMishinCuF( + identifier, F0, F2, f3, f4, f5, f6, a3, a4, a5, a6, d3, d4, d5, species=["*"] + ): + return ExtendedMishinCuF( + identifier=identifier, + F0=F0, + F2=F2, + f3=f3, + f4=f4, + f5=f5, + f6=f6, + a3=a3, + a4=a4, + a5=a5, + a6=a6, + d3=d3, + d4=d4, + d5=d5, + species=species, + ) + + @staticmethod + def RsMinusRPowN(identifier, S, rs, N, species=["*", "*"], cutoff=None): + return RsMinusRPowN(identifier, S, rs, N, species, cutoff=cutoff) @staticmethod def sum(identifier, species=["*", "*"]): @@ -983,10 +1005,12 @@ def __init__( N=None, species=None, is_screening_function=False, + cutoff=None, ): super().__init__( identifier, species=species, is_screening_function=is_screening_function ) + self.cutoff = cutoff self.parameters.add_parameter( "S", start_val=S, @@ -1016,7 +1040,11 @@ def func(r): return func def _to_xml_element(self): - return super()._to_xml_element(name="RsMinusRPowN") + xml = super()._to_xml_element(name="RsMinusRPowN") + if self.cutoff is not None: + cutoff = ET.SubElement(xml, "cutoff") + cutoff.text = f"{self.cutoff}" + return xml class Constant(SpecialFunction): @@ -1134,7 +1162,7 @@ def __init__( q4=None, Q1=None, Q2=None, - species=["*", "*"], + species=["*"], ): super().__init__(identifier, species=species, is_screening_function=False) self.parameters.add_parameter( @@ -1182,6 +1210,96 @@ def _to_xml_element(self): return super()._to_xml_element(name="Mishin-Cu-F") +class ExtendedMishinCuF(SpecialFunction): + def __init__( + self, + identifier=None, + F0=None, + F2=None, + f3=None, + f4=None, + f5=None, + f6=None, + a3=None, + a4=None, + a5=None, + a6=None, + d3=None, + d4=None, + d5=None, + species=["*"], + ): + super().__init__(identifier, species=species, is_screening_function=False) + self.parameters.add_parameter( + "F0", + start_val=F0, + enabled=True, + ) + self.parameters.add_parameter( + "F2", + start_val=F2, + enabled=True, + ) + self.parameters.add_parameter( + "f3", + start_val=f3, + enabled=True, + ) + self.parameters.add_parameter( + "f4", + start_val=f4, + enabled=True, + ) + self.parameters.add_parameter( + "f5", + start_val=f5, + enabled=True, + ) + self.parameters.add_parameter( + "f6", + start_val=f6, + enabled=True, + ) + self.parameters.add_parameter( + "a3", + start_val=a3, + enabled=True, + ) + self.parameters.add_parameter( + "a4", + start_val=a4, + enabled=True, + ) + self.parameters.add_parameter( + "a5", + start_val=a5, + enabled=True, + ) + self.parameters.add_parameter( + "a6", + start_val=a6, + enabled=True, + ) + self.parameters.add_parameter( + "d3", + start_val=d3, + enabled=True, + ) + self.parameters.add_parameter( + "d4", + start_val=d4, + enabled=True, + ) + self.parameters.add_parameter( + "d5", + start_val=d5, + enabled=True, + ) + + def _to_xml_element(self): + return super()._to_xml_element(name="Extended-Mishin-Cu-F") + + class UserFunction(DataContainer, BaseFunctionMixin): """ Analytic functions that are not implemented in atomicrex diff --git a/pyiron_contrib/atomistics/atomicrex/general_input.py b/pyiron_contrib/atomistics/atomicrex/general_input.py index 04b687b71..e0d3a8880 100644 --- a/pyiron_contrib/atomistics/atomicrex/general_input.py +++ b/pyiron_contrib/atomistics/atomicrex/general_input.py @@ -51,31 +51,36 @@ def __init__( self.output_file = "atomicrex.out" self.parameter_constraints = ParameterConstraints() - def _write_xml_file(self, directory): + def _write_xml_file(self, directory, job=None): """Internal function. Write the main input xml file in a directory. Args: directory (str): Working directory """ - job = ET.Element("job") + root = ET.Element("job") - output_file = ET.SubElement(job, "output-file") + output_file = ET.SubElement(root, "output-file") output_file.text = self.output_file - name = ET.SubElement(job, "name") + name = ET.SubElement(root, "name") name.text = self.name - verbosity = ET.SubElement(job, "verbosity") + verbosity = ET.SubElement(root, "verbosity") verbosity.text = self.verbosity if self.validate_potentials: - validate_potentials = ET.SubElement(job, "validate-potentials") + validate_potentials = ET.SubElement(root, "validate-potentials") - real_precision = ET.SubElement(job, "real-precision") + real_precision = ET.SubElement(root, "real-precision") real_precision.text = f"{self.real_precision}" - atom_types = ET.SubElement(job, "atom-types") + atom_types = ET.SubElement(root, "atom-types") + if len(self.atom_types) == 0: + eles = job.structures._structures.get_elements() + for ele in eles: + self.atom_types[ele] = None + for k, v in self.atom_types.items(): species = ET.SubElement(atom_types, "species") species.text = k @@ -89,7 +94,7 @@ def _write_xml_file(self, directory): species.set("atomic-number", f"{index}") if not isinstance(self.fit_algorithm, ScipyAlgorithm): - fitting = ET.SubElement(job, "fitting") + fitting = ET.SubElement(root, "fitting") if self.enable_fitting: fitting.set("enabled", "true") else: @@ -97,21 +102,21 @@ def _write_xml_file(self, directory): fitting.set("output-interval", f"{self.output_interval}") fitting.append(self.fit_algorithm._to_xml_element()) - potentials = ET.SubElement(job, "potentials") + potentials = ET.SubElement(root, "potentials") include = ET.SubElement(potentials, "xi:include") include.set("href", "potential.xml") include.set("xmlns:xi", "http://www.w3.org/2003/XInclude") - structures = ET.SubElement(job, "structures") + structures = ET.SubElement(root, "structures") include = ET.SubElement(structures, "xi:include") include.set("href", "structures.xml") include.set("xmlns:xi", "http://www.w3.org/2003/XInclude") if len(self.parameter_constraints) > 0: - job.append(self.parameter_constraints._to_xml_element()) + root.append(self.parameter_constraints._to_xml_element()) file_name = posixpath.join(directory, "main.xml") - write_pretty_xml(job, file_name) + write_pretty_xml(root, file_name) class AtomTypes(DataContainer): @@ -527,6 +532,90 @@ def gn_isres( seed=seed, ) + @staticmethod + def g_mlsl( + stopval=1e-10, + max_iter=50, + maxtime=None, + ftol_rel=None, + ftol_abs=None, + xtol_rel=None, + seed=None, + ): + return NloptGlobalLocal( + name="G_MLSL", + stopval=stopval, + max_iter=max_iter, + maxtime=maxtime, + ftol_rel=ftol_rel, + ftol_abs=ftol_abs, + xtol_rel=xtol_rel, + seed=seed, + ) + + @staticmethod + def g_mlsl_lds( + stopval=1e-10, + max_iter=50, + maxtime=None, + ftol_rel=None, + ftol_abs=None, + xtol_rel=None, + seed=None, + ): + return NloptGlobalLocal( + name="G_MLSL_LDS", + stopval=stopval, + max_iter=max_iter, + maxtime=maxtime, + ftol_rel=ftol_rel, + ftol_abs=ftol_abs, + xtol_rel=xtol_rel, + seed=seed, + ) + + @staticmethod + def gd_stogo( + stopval=1e-10, + max_iter=50, + maxtime=None, + ftol_rel=None, + ftol_abs=None, + xtol_rel=None, + seed=None, + ): + return NloptAlgorithm( + name="GD_STOGO", + stopval=stopval, + max_iter=max_iter, + maxtime=maxtime, + ftol_rel=ftol_rel, + ftol_abs=ftol_abs, + xtol_rel=xtol_rel, + seed=seed, + ) + + @staticmethod + def gd_stogo_rand( + stopval=1e-10, + max_iter=50, + maxtime=None, + ftol_rel=None, + ftol_abs=None, + xtol_rel=None, + seed=None, + ): + return NloptAlgorithm( + name="GD_STOGO_RAND", + stopval=stopval, + max_iter=max_iter, + maxtime=maxtime, + ftol_rel=ftol_rel, + ftol_abs=ftol_abs, + xtol_rel=xtol_rel, + seed=seed, + ) + @staticmethod def scipy_algorithm(): return ScipyAlgorithm() @@ -565,17 +654,22 @@ def _to_xml_element(self): return algo -class SpaMinimizer: +class SpaMinimizer(DataContainer): """ Global optimizer implemented in atomicrex. Should be used in combination with a local minimizer. See the atomicrex documentation for details. """ - def __init__(self, spa_iterations, seed): - self.spa_iterations = spa_iterations + def __init__(self, max_iter=None, seed=None, *args, **kwargs): + super().__init__(table_name="fitting_algorithm", *args, **kwargs) + self.max_iter = max_iter self.seed = seed self.local_minimizer = None + + @property + def name(self): + return "spa" def _to_xml_element(self): """Internal function. @@ -583,10 +677,12 @@ def _to_xml_element(self): and returns it """ spa = ET.Element("spa") - spa.set("max-iter", f"{self.spa_iterations}") + spa.set("max-iter", f"{self.max_iter}") spa.set("seed", f"{self.seed}") if self.local_minimizer is not None: spa.append(self.local_minimizer._to_xml_element()) + else: + raise ValueError("Set a local minimizer for Spa") return spa @@ -637,6 +733,8 @@ def _to_xml_element(self): nlopt.set("ftol_abs", f"{self.ftol_abs}") if self.xtol_rel is not None: nlopt.set("xtol_rel", f"{self.xtol_rel}") + if self.seed is not None: + nlopt.set("seed", f"{self.seed}") return nlopt @@ -647,14 +745,14 @@ class NloptGlobalLocal(NloptAlgorithm): def __init__( self, - stopval, - max_iter, - maxtime, - ftol_rel, - ftol_abs, - xtol_rel, - name, - seed, + stopval=None, + max_iter=None, + maxtime=None, + ftol_rel=None, + ftol_abs=None, + xtol_rel=None, + name=None, + seed=None, *args, **kwargs, ): @@ -706,12 +804,32 @@ def to_hdf(self, hdf, group_name): with hdf.open(group_name) as h: self._type_to_hdf(h) h["global_minimizer"] = self.global_minimizer - h.put("local_minimizer_kwargs", self.local_minimizer_kwargs) - h.put("global_minimizer_kwargs", self.global_minimizer_kwargs) + """ + with h.open("local_minimizer_kwargs") as loc_hdf: + for k, v in self.local_minimizer_kwargs.items(): + try: + loc_hdf[k] = v + except TypeError: + loc_hdf[k] = v.__name__ + with h.open("global_minimizer_kwargs") as glob_hdf: + for k, v in self.global_minimizer_kwargs.items(): + if isinstance(v, dict): + with glob_hdf.open(k) as v_hdf: + for k, v in self.v.items(): + try: + v_hdf[k] = v + except TypeError: + v_hdf[k] = v.__name__ + else: + try: + glob_hdf[k] = v + except TypeError: + glob_hdf[k] = v.__name__ + """ def from_hdf(self, hdf, group_name): with hdf.open(group_name) as h: - self._type_to_hdf(h) + #self._type_from_hdf(h) self.global_minimizer = h["global_minimizer"] def _type_to_hdf(self, hdf): diff --git a/pyiron_contrib/atomistics/atomicrex/interactive.py b/pyiron_contrib/atomistics/atomicrex/interactive.py index 87134041b..936b936ee 100644 --- a/pyiron_contrib/atomistics/atomicrex/interactive.py +++ b/pyiron_contrib/atomistics/atomicrex/interactive.py @@ -1,7 +1,7 @@ import posixpath, os, time from scipy import optimize -from pyiron_base.job.interactive import InteractiveBase +from pyiron_base import InteractiveBase from pyiron_contrib.atomistics.atomicrex.general_input import ScipyAlgorithm from pyiron_contrib.atomistics.atomicrex.base import AtomicrexBase @@ -87,12 +87,13 @@ def run_if_interactive(self): if isinstance(self.input.fit_algorithm, ScipyAlgorithm): self._scipy_run() # sleep between running and collecting so atomicrex output is flushed to file - time.sleep(2.0) + ## close to flush outputs to file + self.interactive_close() self._scipy_collect(cwd=self.path) else: self._interactive_library.perform_fitting() - ## Delete the atomicrex object at the end to flush outputs to file - del self._interactive_library + ## close to flush outputs to file + self.interactive_close() self.collect_output(cwd=self.path) def _scipy_run(self): @@ -111,15 +112,12 @@ def _scipy_run(self): **self.input.fit_algorithm.global_minimizer_kwargs, ) - self._interactive_library.set_potential_parameters(res.x) + #self._interactive_library.set_potential_parameters(res.x) self.output.residual = self._interactive_library.calculate_residual() self.output.iterations = res.nit - print(res) self._interactive_library.print_potential_parameters() self._interactive_library.print_properties() self._interactive_library.output_results() - ## Delete the atomicrex object at the end to flush outputs to file - del self._interactive_library return res def _scipy_collect(self, cwd=None): @@ -129,7 +127,7 @@ def _scipy_collect(self, cwd=None): """ if cwd is None: cwd = self.working_directory - if self.input.__version__ == "0.1.0": + if self.input.__version__ >= "0.1.0": filepath = f"{cwd}/atomicrex.out" params_triggered = False diff --git a/pyiron_contrib/atomistics/atomicrex/potential_factory.py b/pyiron_contrib/atomistics/atomicrex/potential_factory.py index 28955a796..a7c2dd24d 100644 --- a/pyiron_contrib/atomistics/atomicrex/potential_factory.py +++ b/pyiron_contrib/atomistics/atomicrex/potential_factory.py @@ -37,6 +37,22 @@ def eam_potential( species=species, ) + @staticmethod + def adp_potential( + identifier="ADP", + export_file="output.adp.fs", + rho_range_factor=2.0, + resolution=10000, + species=["*", "*"], + ): + return ADPotential( + identifier=identifier, + export_file=export_file, + rho_range_factor=rho_range_factor, + resolution=resolution, + species=species, + ) + @staticmethod def meam_potential(identifier="MEAM", export_file="meam.out", species=["*", "*"]): return MEAMPotential( @@ -556,6 +572,57 @@ def count_parameters(self, enabled_only=True): parameters += f.count_parameters(enabled_only=enabled_only) return parameters + @property + def _function_tuple(self): + raise NotImplementedError("Implement a tuple with functions in subclass") + + @property + def _function_dict(self): + raise NotImplementedError("Implement a tuple with functions in subclass") + + def _mapping_functions_xml(self, pot): + mappingxml = ET.SubElement(pot, "mapping") + functionsxml = ET.SubElement(pot, "functions") + + for k, functions in self._function_dict.items(): + for f in functions.values(): + fxml = ET.SubElement(mappingxml, k) + if len(f.species) == 1: + fxml.set("species", f"{f.species[0]}") + elif len(f.species) == 2: + fxml.set("species-a", f"{f.species[0]}") + fxml.set("species-b", f"{f.species[1]}") + elif len(f.species) == 2: + fxml.set("species-a", f"{f.species[0]}") + fxml.set("species-b", f"{f.species[1]}") + fxml.set("species-c", f"{f.species[2]}") + fxml.set("function", f"{f.identifier}") + functionsxml.append(f._to_xml_element()) + + def _eam_parse_final_parameters(self, lines): + """ + Internal Function. + Parse function parameters from atomicrex output. + + Args: + lines (list[str]): atomicrex output lines + + Raises: + KeyError: Raises if a parsed parameter can't be matched to a function. + """ + for l in lines: + identifier, leftover, value = _parse_parameter_line(l) + found = False + for functions in self._function_tuple: + if identifier in functions: + functions[identifier]._parse_final_parameter(leftover, value) + found = True + if not found: + raise KeyError( + f"Can't find {identifier} in potential, probably something went wrong during parsing.\n" + "Fitting parameters of screening functions probably doesn't work right now" + ) + class EAMPotential(AbstractPotential, EAMlikeMixin): """ @@ -724,25 +791,37 @@ def plot_final_potential(self, job, filename=None): squeeze=False, ) for i, (el, pot_dict) in enumerate(elements.items()): - for j, (pot, y) in enumerate(pot_dict.items()): + V_count = 0 + rho_count = 0 + for pot, y in pot_dict.items(): if pot == "F": xdata = rho_values xlabel = "$\\rho $ [a.u.]" k = 0 - ylim = (-5, 5) + ylim = (np.min(y) - 0.5, 5) + ax[i * 3 + k, 0].plot(xdata, y) + ax[i * 3 + k, 0].set(ylim=ylim, title=f"{el} {pot}", xlabel=xlabel) elif "rho" in pot: xdata = r_values xlabel = "r [$\AA$]" k = 1 - ylim = (-0.2, 1) + ylim = (np.min(y) - 0.1, 1) + ax[i * 3 + k, rho_count].plot(xdata, y) + ax[i * 3 + k, rho_count].set( + ylim=ylim, title=f"{el} {pot}", xlabel=xlabel + ) + rho_count += 1 elif "V" in pot: xdata = r_values[1:] y = y[1:] xlabel = "r [$\AA$]" k = 2 - ylim = (-2, 2) - ax[i + k, 0].plot(xdata, y) - ax[i + k, 0].set(ylim=ylim, title=f"{el} {pot}", xlabel=xlabel) + ylim = (np.min(y) - 0.1, 2) + ax[i * 3 + k, V_count].plot(xdata, y) + ax[i * 3 + k, V_count].set( + ylim=ylim, title=f"{el} {pot}", xlabel=xlabel + ) + V_count += 1 return fig, ax def count_local_extrema( @@ -766,6 +845,116 @@ def count_local_extrema( return extrema_dict +class ADPotential(AbstractPotential, EAMlikeMixin): + """ + Angular dependent potential. + Usage: Create using the potential factory class. + Add functions defined using the function_factory + to self.pair_interactions, self.electron_densities + and self.embedding_energies, self.u_functions and + self.w_functions in dictionary style, + using the identifier of the function as key. + Example: + eam.pair_interactions["V"] = morse_function + + """ + + def __init__( + self, + init=None, + identifier=None, + export_file=None, + rho_range_factor=None, + resolution=None, + species=None, + ): + + super().__init__(init=init) + if init is None: + self.pair_interactions = DataContainer(table_name="pair_interactions") + self.electron_densities = DataContainer(table_name="electron_densities") + self.embedding_energies = DataContainer(table_name="embedding_energies") + self.u_functions = DataContainer(table_name="u_functions") + self.w_functions = DataContainer(table_name="w_functions") + self.identifier = identifier + self.export_file = export_file + self.rho_range_factor = rho_range_factor + self.resolution = resolution + self.species = species + + @property + def _function_tuple(self): + return ( + self.pair_interactions, + self.electron_densities, + self.embedding_energies, + self.u_functions, + self.w_functions, + ) + + @property + def _function_dict(self): + return { + "pair-interaction": self.pair_interactions, + "electron-density": self.electron_densities, + "embedding-energy": self.embedding_energies, + "u-function": self.u_functions, + "w-function": self.w_functions, + } + + def _potential_as_pd_df(self, job): + """ + Makes the tabulated eam potential written by atomicrex usable + for pyiron lammps jobs. + """ + if self.export_file is None: + raise ValueError("export_file must be set to use the potential with lammps") + + species = [el for el in job.input.atom_types.keys()] + species_str = "" + for s in species: + species_str += f"{s} " + + pot = pd.DataFrame( + { + "Name": f"{self.identifier}", + "Filename": [[f"{job.working_directory}/{self.export_file}"]], + "Model": ["Custom"], + "Species": [species], + "Config": [ + [ + "pair_style adp\n", + f"pair_coeff * * {job.working_directory}/{self.export_file} {species_str}\n", + ] + ], + } + ) + return pot + + def write_xml_file(self, directory): + """ + Internal function to convert to an xml element + """ + adp = ET.Element("adp") + adp.set("id", f"{self.identifier}") + adp.set("species-a", f"{self.species[0]}") + adp.set("species-b", f"{self.species[1]}") + + if self.export_file: + export = ET.SubElement(adp, "export-adp-file") + export.set("resolution", f"{self.resolution}") + export.set("rho-range-factor", f"{self.rho_range_factor}") + export.text = f"{self.export_file}" + + self._mapping_functions_xml(adp) + + filename = posixpath.join(directory, "potential.xml") + write_pretty_xml(adp, filename) + + def _parse_final_parameters(self, lines): + return self._eam_parse_final_parameters(lines) + + class MEAMPotential(AbstractPotential, EAMlikeMixin): def __init__(self, init=None, identifier=None, export_file=None, species=None): super().__init__(init=init) diff --git a/pyiron_contrib/atomistics/atomicrex/structure_list.py b/pyiron_contrib/atomistics/atomicrex/structure_list.py index fa1ebc410..844c8faf9 100644 --- a/pyiron_contrib/atomistics/atomicrex/structure_list.py +++ b/pyiron_contrib/atomistics/atomicrex/structure_list.py @@ -7,11 +7,15 @@ import numpy as np from numpy import ndarray -from pyiron_base import state, DataContainer +from pyiron_base import state, DataContainer, FlattenedStorage from pyiron_atomistics import Atoms, ase_to_pyiron from pyiron_atomistics.atomistics.structure.structurestorage import StructureStorage -from pyiron_contrib.atomistics.atomistics.job.trainingcontainer import TrainingPlots +from pyiron_contrib.atomistics.atomistics.job.trainingcontainer import ( + TrainingPlots, + TrainingContainer, + TrainingStorage, +) from pyiron_contrib.atomistics.atomicrex.fit_properties import ( ARFitPropertyList, ARFitProperty, @@ -32,16 +36,16 @@ class ARStructureContainer: __version__ = "0.3.0" __hdf_version__ = "0.3.0" - def __init__(self, num_atoms=1, num_structures=1): + def __init__(self, num_atoms=0, num_structures=0): self.fit_properties = DataContainer(table_name="fit_properties") self._structures = StructureStorage( num_atoms=num_atoms, num_structures=num_structures ) self._predefined_storage = DataContainer(table_name="predefined_structures") - self._structures.add_array("fit", dtype=bool, per="chunk") - self._structures.add_array("clamp", dtype=bool, per="chunk") - self._structures.add_array("predefined", dtype=bool, per="chunk") - self._structures.add_array("relative_weight", per="chunk") + self._structures.add_array("fit", dtype=bool, per="chunk", fill=True) + self._structures.add_array("clamp", dtype=bool, per="chunk", fill=True) + self._structures.add_array("predefined", dtype=bool, per="chunk", fill=False) + self._structures.add_array("relative_weight", per="chunk", fill=1.0) self.structure_file_path = None try: self._interactive_library = atomicrex.Job() @@ -299,6 +303,11 @@ def get_vector_property(self, prop, identifier, final=True): else: return self.fit_properties[prop]._per_element_arrays["target_val"][slc] + def _sync(self): + for flat in self.fit_properties.values(): + flat.num_elements = self._structures.num_elements + flat.num_chunks = self._structures.num_chunks + def _shrink(self): self._resize_all( num_chunks=self._structures.num_chunks, @@ -517,6 +526,83 @@ def prepare_plotting(self, final_values=False): * self._structures.length ) + #### PotentialFit methods + def add_training_data(self, container: TrainingContainer) -> None: + storage = container._container + atomic_energy_storage = FlattenedARScalarProperty( + num_chunks=storage.num_chunks, num_elements=storage.num_elements + ) + atomic_energy_storage.num_chunks = storage.num_chunks + atomic_energy_storage.num_elements = storage.num_elements + atomic_energy_storage._per_chunk_arrays["fit"][0 : storage.num_chunks] = True + atomic_energy_storage._per_chunk_arrays["tolerance"][ + 0 : storage.num_chunks + ] = 0.001 + atomic_energy_storage._per_chunk_arrays["target_val"] = ( + storage._per_chunk_arrays["energy"][0 : storage.num_chunks] + / storage.length[0 : storage.num_chunks] + ) + + atomic_forces_storage = FlattenedARVectorProperty( + num_chunks=storage.num_chunks, num_elements=storage.num_elements + ) + atomic_forces_storage.num_chunks = storage.num_chunks + atomic_forces_storage.num_elements = storage.num_elements + atomic_forces_storage._per_chunk_arrays["fit"][0 : storage.num_chunks] = True + atomic_forces_storage._per_chunk_arrays["tolerance"][ + 0 : storage.num_chunks + ] = 0.01 + atomic_forces_storage._per_element_arrays[ + "target_val" + ] = storage._per_element_arrays["forces"][0 : storage.num_elements] + + if "atomic-energy" not in self.fit_properties: + self.fit_properties["atomic-energy"] = FlattenedARScalarProperty( + num_chunks=self._structures.num_chunks, + num_elements=self._structures.num_elements, + ) + if "atomic-forces" not in self.fit_properties: + self.fit_properties["atomic-forces"] = FlattenedARVectorProperty( + num_chunks=self._structures.num_chunks, + num_elements=self._structures.num_elements, + ) + self._sync() + + self._structures.extend(storage) + self.fit_properties["atomic-energy"].extend(atomic_energy_storage) + self.fit_properties["atomic-forces"].extend(atomic_forces_storage) + + def _to_TrainingStorage(self, final: bool = False) -> TrainingStorage: + self._shrink() + storage = TrainingStorage() + storage.extend(self._structures) + if final: + val_str = "final_val" + else: + val_str = "target_val" + storage._per_chunk_arrays["energy"][0 : storage.num_chunks] = ( + self.fit_properties["atomic-energy"][val_str] + * self._structures._per_chunk_arrays["length"] + ) + storage._per_element_arrays["forces"][ + 0 : storage.num_elements + ] = self.fit_properties["atomic-forces"][val_str] + return storage + + def get_training_data(self) -> TrainingStorage: + return self._to_TrainingStorage(final=False) + + def get_predicted_data(self) -> FlattenedStorage: + return self._to_TrainingStorage(final=True) + + @property + def training_data(self): + return self.get_training_data() + + @property + def predicted_data(self): + return self.get_predicted_data() + ### This is probably useless like this in most cases because forces can't be passed. def user_structure_to_xml_element(structure): @@ -761,3 +847,15 @@ def predefined_structure_xml( else: struct_xml.append(fit_properties) return struct_xml + + +def sort_structure(structure, forces=None): + sort_array = get_sort_array(structure) + if forces is None: + return structure[sort_array] + return structure[sort_array], forces[sort_array] + + +def get_sort_array(structure): + sort_array = np.argsort(structure.numbers, kind="stable") + return sort_array diff --git a/pyiron_contrib/atomistics/atomistics/job/__init__.py b/pyiron_contrib/atomistics/atomistics/job/__init__.py index e69de29bb..84fcdbee3 100644 --- a/pyiron_contrib/atomistics/atomistics/job/__init__.py +++ b/pyiron_contrib/atomistics/atomistics/job/__init__.py @@ -0,0 +1 @@ +from .trainingcontainer import TrainingContainer, TrainingStorage \ No newline at end of file diff --git a/pyiron_contrib/atomistics/atomistics/job/structurestorage.py b/pyiron_contrib/atomistics/atomistics/job/structurestorage.py deleted file mode 100644 index 22b11ccbf..000000000 --- a/pyiron_contrib/atomistics/atomistics/job/structurestorage.py +++ /dev/null @@ -1,7 +0,0 @@ -from pyiron_atomistics.atomistics.structure.structurestorage import StructureStorage as StructureStorageBase -from pyiron_base import deprecate - -class StructureStorage(StructureStorageBase): - @deprecate("import from pyiron_atomistics.atomistics.structure.structurestorage instead") - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) diff --git a/pyiron_contrib/atomistics/atomistics/job/trainingcontainer.py b/pyiron_contrib/atomistics/atomistics/job/trainingcontainer.py index 076c7828d..017eaab0a 100644 --- a/pyiron_contrib/atomistics/atomistics/job/trainingcontainer.py +++ b/pyiron_contrib/atomistics/atomistics/job/trainingcontainer.py @@ -14,7 +14,7 @@ >>> structure = pr.create.structure.ase_bulk("Fe") >>> forces = numpy.array([-1, 1, -1]) ->>> container.include_structure(structure, energy=-1.234, forces=forces, name="Fe_bcc") +>>> container.add_structure(structure, energy=-1.234, forces=forces, identifier="Fe_bcc") If you have a lot of precomputed structures you may also add them in bulk from a pandas DataFrame @@ -28,15 +28,20 @@ Fe_bcc ... """ +from typing import Callable, Dict, Any, Optional + from warnings import catch_warnings import numpy as np import pandas as pd import seaborn as sns import matplotlib.pyplot as plt -from pyiron_contrib.atomistics.atomistics.job.structurestorage import StructureStorage + +from ase.atoms import Atoms as ASEAtoms + from pyiron_atomistics.atomistics.structure.atoms import Atoms from pyiron_atomistics.atomistics.structure.has_structure import HasStructure +from pyiron_atomistics.atomistics.structure.structurestorage import StructureStorage, StructurePlots from pyiron_atomistics.atomistics.structure.neighbors import NeighborsTrajectory from pyiron_base import GenericJob, DataContainer, deprecate @@ -52,10 +57,10 @@ def __init__(self, project, job_name): self.__hdf_version__ = "0.3.0" self._container = TrainingStorage() - self.input = DataContainer({ - "save_neighbors": True, - "num_neighbors": 12 - }, table_name="parameters") + self.input = DataContainer( + {"save_neighbors": True, "num_neighbors": 12}, + table_name="parameters" + ) def include_job(self, job, iteration_step=-1): """ @@ -63,12 +68,39 @@ def include_job(self, job, iteration_step=-1): Args: job (:class:`.AtomisticGenericJob`): job to take structure from - iteration_step (int, optional): if job has multiple steps, this selects which to add + iteration_step (int, optional): if job has multiple steps, this + selects which to add """ self._container.include_job(job, iteration_step) - @deprecate("Use add_structure instead") - def include_structure(self, structure, energy, forces=None, stress=None, name=None): + def include_structure( + self, + structure, + energy=None, + name=None, + **properties + ): + """ + Add new structure to structure list and save energy and forces with it. + + For consistency with the rest of pyiron, energy should be in units of eV + and forces in eV/A, but no conversion is performed. + + Args: + structure_or_job (:class:`~.Atoms`): structure to add + energy (float): energy of the whole structure + forces (Nx3 array of float, optional): per atom forces, where N is + the number of atoms in the structure + stress (6 array of float, optional): per structure stresses in voigt + notation + name (str, optional): name describing the structure + """ + self._container.include_structure(structure, name=name, energy=energy, + **properties) + + def add_structure( + self, structure, energy, forces=None, stress=None, identifier=None, **arrays + ): """ Add new structure to structure list and save energy and forces with it. @@ -82,7 +114,9 @@ def include_structure(self, structure, energy, forces=None, stress=None, name=No stress (6 array of float, optional): per structure stresses in voigt notation name (str, optional): name describing the structure """ - self._container.include_structure(structure, energy, forces, stress, name) + self._container.add_structure( + structure, energy, identifier=identifier, forces=forces, stress=stress, **arrays + ) def include_dataset(self, dataset): """ @@ -118,8 +152,11 @@ def get_neighbors(self, num_neighbors=None): """ if num_neighbors is None: num_neighbors = self.input.num_neighbors - n = NeighborsTrajectory(has_structure=self, store=self._container if self.input.save_neighbors else None, - num_neighbors=num_neighbors) + n = NeighborsTrajectory( + has_structure=self, + store=self._container if self.input.save_neighbors else None, + num_neighbors=num_neighbors, + ) n.compute_neighbors() return n @@ -161,6 +198,9 @@ def to_list(self, filter_function=None): """ return self._container.to_list(filter_function) + def to_dict(self): + return self._container.to_dict() + def write_input(self): pass @@ -187,7 +227,9 @@ def from_hdf(self, hdf=None, group_name=None): super().from_hdf(hdf=hdf, group_name=group_name) hdf_version = self.project_hdf5.get("HDF_VERSION", "0.1.0") if hdf_version == "0.1.0": - table = pd.read_hdf(self.project_hdf5.file_name, self.name + "/output/structure_table") + table = pd.read_hdf( + self.project_hdf5.file_name, self.name + "/output/structure_table" + ) self.include_dataset(table) else: self._container = TrainingStorage() @@ -195,150 +237,63 @@ def from_hdf(self, hdf=None, group_name=None): if hdf_version == "0.3.0": self.input.from_hdf(self.project_hdf5, "parameters") - @property - def plot(self): + def sample( + self, name: str, selector: Callable[[StructureStorage, int], bool], + delete_existing_job: bool = False + ) -> "TrainingContainer": """ - :class:`.TrainingPlots`: plotting interface - """ - return TrainingPlots(self._container) - -class TrainingPlots: - """ - Simple interface to plot various properties of the structures inside the given :class:`.TrainingContainer`. - """ + Create a new TrainingContainer with structures filtered by selector. - __slots__ = "_train" - - def __init__(self, train): - self._train = train - - def _calc_spacegroups(self, symprec=1e-3): - """ - Calculate space groups of all structures. + `self` must have status `finished`. `selector` is passed the underlying :class:`StructureStorage` of this + container and the index of the structure and return a boolean whether to include the structure in the new + container or not. The new container is saved and run. Args: - symprec (float): symmetry precision given to spglib + name (str): name of the new TrainingContainer + selector (Callable[[StructureStorage, int], bool]): callable that selects structure to include + delete_existing_job (bool): if job with name exist, remove it first Returns: - DataFrame: contains columns 'crystal_system' (str) and 'space_group' (int) for each structure - """ - def get_crystal_system(num): - if num in range(1,3): - return "triclinic" - elif num in range(3, 16): - return "monoclinic" - elif num in range(16, 75): - return "orthorombic" - elif num in range(75, 143): - return "trigonal" - elif num in range(143, 168): - return "tetragonal" - elif num in range(168, 195): - return "hexagonal" - elif num in range(195, 230): - return "cubic" - - def extract(s): - spg = s.get_symmetry(symprec=symprec).spacegroup["Number"] - return {'space_group': spg, 'crystal_system': get_crystal_system(spg)} - - return pd.DataFrame(map(extract, self._train.iter_structures())) + :class:`.TrainingContainer`: new container with selected structures - def cell(self): + Raises: + ValueError: if a job with the given `name` already exists. """ - Plot histograms of cell parameters. + if not self.status.finished: + raise ValueError(f"Job must be finished, not '{self.status}'!") + cont = self.project.create.job.TrainingContainer(name, delete_existing_job=delete_existing_job) + if not cont.status.initialized: + raise ValueError(f"Job '{name}' already exists with status: {cont.status}!") + cont._container = self._container.sample(selector) + cont.run() + return cont - Plotted are atomic volume, density, cell vector lengths and cell vector angles in separate subplots all on a - log-scale. + @property + def plot(self): + """ + :class:`.TrainingPlots`: plotting interface + """ + return self._container.plot - Returns: - `DataFrame`: contains the plotted information in the columns: - - a: length of first vector - - b: length of second vector - - c: length of third vector - - alpha: angle between first and second vector - - beta: angle between second and third vector - - gamma: angle between third and first vector - - V: volume of the cell - - N: number of atoms in the cell - """ - N = self._train.get_array("length") - C = self._train.get_array("cell") - - def get_angle(cell, idx=0): - return np.arccos(np.dot(cell[idx], cell[(idx+1)%3]) \ - / np.linalg.norm(cell[idx]) / np.linalg.norm(cell[(idx+1)%3])) - - def extract(n, c): - return { - "a": np.linalg.norm(c[0]), - "b": np.linalg.norm(c[1]), - "c": np.linalg.norm(c[2]), - "alpha": get_angle(c, 0), - "beta": get_angle(c, 1), - "gamma": get_angle(c, 2), - } - df = pd.DataFrame([extract(n, c) for n, c in zip(N, C)]) - df["V"] = np.linalg.det(C) - df["N"] = N - - plt.subplot(1, 4, 1) - plt.title("Atomic Volume") - plt.hist(df.V/df.N, bins=20, log=True) - plt.xlabel(r"$V$ [$\AA^3$]") - - plt.subplot(1, 4, 2) - plt.title("Density") - plt.hist(df.N/df.V, bins=20, log=True) - plt.xlabel(r"$\rho$ [$\AA^{-3}$]") - - plt.subplot(1, 4, 3) - plt.title("Lattice Vector Lengths") - plt.hist([df.a, df.b, df.c], log=True) - plt.xlabel(r"$a,b,c$ [$\AA$]") - - plt.subplot(1, 4, 4) - plt.title("Lattice Vector Angles") - plt.hist([df.alpha, df.beta, df.gamma], log=True) - plt.xlabel(r"$\alpha,\beta,\gamma$") + def iter(self, *arrays, wrap_atoms=True): + """ + Iterate over all structures in this object and all arrays that are defined - return df + Args: + wrap_atoms (bool): True if the atoms are to be wrapped back into the unit cell; passed to + :meth:`.get_structure()` + *arrays (str): name of arrays that should be iterated over - def spacegroups(self, symprec=1e-3): + Yields: + :class:`pyiron_atomistics.atomistitcs.structure.atoms.Atoms`, arrays: every structure attached to the object and queried arrays """ - Plot histograms of space groups and crystal systems. - - Spacegroups and crystal systems are plotted in separate subplots. + yield from self._container.iter(*arrays, wrap_atoms=wrap_atoms) - Args: - symprec (float): precision of the symmetry search (passed to spglib) - Returns: - DataFrame: contains two columns "space_group", "crystal_system" - for each structure in `train` - """ - - df = self._calc_spacegroups(symprec=symprec) - plt.subplot(1, 2, 1) - plt.hist(df.space_group, bins=230) - plt.xlabel("Space Group") - - plt.subplot(1, 2, 2) - l, h = np.unique(df.crystal_system, return_counts=True) - sort_key = { - "triclinic": 1, - "monoclinic": 3, - "orthorombic": 16, - "trigonal": 75, - "tetragonal": 143, - "hexagonal": 168, - "cubic": 195, - } - I = np.argsort([sort_key[ll] for ll in l]) - plt.bar(l[I], h[I]) - plt.xlabel("Crystal System") - plt.xticks(rotation=35) - return df +class TrainingPlots(StructurePlots): + """ + Simple interface to plot various properties of the structures inside the given :class:`.TrainingContainer`. + """ def energy_volume(self, crystal_systems=False): """ @@ -354,9 +309,9 @@ def energy_volume(self, crystal_systems=False): also contain space groups and crystal systems of each structure """ - N = self._train.get_array("length") - E = self._train.get_array("energy") / N - C = self._train.get_array("cell") + N = self._store.get_array("length") + E = self._store.get_array("energy") / N + C = self._store.get_array("cell") V = np.linalg.det(C) / N df = pd.DataFrame({"V": V, "E": E}) @@ -374,78 +329,28 @@ def energy_volume(self, crystal_systems=False): return df - def coordination(self, num_shells=4, log=True): + def forces(self, axis: Optional[int] = None): """ - Plot histogram of coordination in neighbor shells. - - Computes one histogram of the number of neighbors in each neighbor shell up to `num_shells` and then plots them - together. + Plot a histogram of all forces. Args: - num_shells (int): maximum shell to plot - log (float): plot histogram values on a log scale + axis (int, optional): plot only forces along this axis, if not given plot all forces """ - if not self._train.has_array("shells"): - raise ValueError( - "TrainingContainer contains no neighbor information, call TrainingContainer.get_neighbors first!" - ) - shells = self._train.get_array('shells') - shell_index = shells[np.newaxis, :, :] == np.arange(1, num_shells+1)[:, np.newaxis, np.newaxis] - neigh_count = shell_index.sum(axis=-1) - ticks = np.arange(neigh_count.min(), neigh_count.max()+1) - plt.hist(neigh_count.T, bins=ticks-0.5, - log=True, label=[f"{i}." for i in range(1, num_shells+1)]) - plt.xticks(ticks) - plt.xlabel("Number of Neighbors") - plt.legend(title="Shell") - plt.title("Neighbor Coordination in Shells") - - def shell_distances(self, num_shells=4): - """ - Plot a violin plot of the neighbor distances in shells up to `num_shells`. + f = self._store.get_array("forces") + if axis is not None: + f = f[:, axis] + else: + f = f.ravel() + plt.hist(f, bins=20) + plt.xlabel(r"Force [eV/$\mathrm{\AA}$]") - Args: - num_shells (int): maximum shell to plot - """ - if not self._train.has_array("shells") or not self._train.has_array("distances"): - raise ValueError( - "TrainingContainer contains no neighbor information, call TrainingContainer.get_neighbors first!" - ) - dists = self._train.get_array('distances') - R = dists.flatten() - shells = self._train.get_array('shells') - S = shells.ravel() - d = pd.DataFrame({"distance": R[S < num_shells + 1], "shells": S[S < num_shells + 1]}) - sns.violinplot(y=d.shells, x=d.distance, scale='width', orient='h') - plt.xlabel(r"Distance [$\AA$]") - plt.ylabel("Shell") class TrainingStorage(StructureStorage): def __init__(self): super().__init__() self.add_array("energy", dtype=np.float64, per="chunk", fill=np.nan) - self.add_array("forces", shape=(3,), dtype=np.float64, per="element", fill=np.nan) - # save stress in voigt notation - self.add_array("stress", shape=(6,), dtype=np.float64, per="chunk", fill=np.nan) self._table_cache = None - - @property - def _table(self): - if self._table_cache is None or len(self._table_cache) != len(self): - self._table_cache = pd.DataFrame({ - "name": [self.get_array("identifier", i) - for i in range(len(self))], - "atoms": [self.get_structure(i) - for i in range(len(self))], - "energy": [self.get_array("energy", i) - for i in range(len(self))], - "forces": [self.get_array("forces", i) - for i in range(len(self))], - "stress": [self.get_array("stress", i) - for i in range(len(self))], - }) - self._table_cache["number_of_atoms"] = [len(s) for s in self._table_cache.atoms] - return self._table_cache + self.to_pandas() def to_pandas(self): """ @@ -462,7 +367,22 @@ def to_pandas(self): Returns: :class:`pandas.DataFrame`: collected structures """ - return self._table + if self._table_cache is None or len(self._table_cache) != len(self): + self._table_cache = pd.DataFrame( + { + "name": [self.get_array("identifier", i) for i in range(len(self))], + "atoms": [self.get_structure(i) for i in range(len(self))], + "energy": [self.get_array("energy", i) for i in range(len(self))], + } + ) + if self.has_array("forces"): + self._table_cache["forces"] = [self.get_array("forces", i) for i in range(len(self))] + if self.has_array("stress"): + self._table_cache["stress"] = [self.get_array("stress", i) for i in range(len(self))] + self._table_cache["number_of_atoms"] = [ + len(s) for s in self._table_cache.atoms + ] + return self._table_cache def include_job(self, job, iteration_step=-1): """ @@ -483,21 +403,39 @@ def include_job(self, job, iteration_step=-1): pp = job["output/generic/stresses"] if pp is None: pp = job.output.pressures - if pp is not None: + if pp is not None and len(pp) > 0: stress = pp[iteration_step] else: stress = None if stress is not None: stress = np.asarray(stress) if stress.shape == (3, 3): - stress = np.array([stress[0, 0], stress[1 ,1], stress[2, 2], - stress[1, 2], stress[0, 2], stress[0, 1]]) - self.include_structure(job.get_structure(iteration_step=iteration_step), - energy=energy, forces=forces, stress=stress, - name=job.name) + stress = np.array( + [ + stress[0, 0], + stress[1, 1], + stress[2, 2], + stress[1, 2], + stress[0, 2], + stress[0, 1], + ] + ) + self.include_structure( + job.get_structure(iteration_step=iteration_step), + energy=energy, + forces=forces, + stress=stress, + name=job.name, + ) @deprecate("Use add_structure instead") - def include_structure(self, structure, energy, forces=None, stress=None, name=None): + def include_structure( + self, + structure, + energy, + name=None, + **properties + ): """ Add new structure to structure list and save energy and forces with it. @@ -511,23 +449,26 @@ def include_structure(self, structure, energy, forces=None, stress=None, name=No stress (6 array of float, optional): per structure stresses in voigt notation name (str, optional): name describing the structure """ - self.add_structure(structure, energy, identifier=name, forces=forces, stress=stress) + self.add_structure(structure, identifier=name, energy=energy, + **properties) + + def add_structure( + self, + structure: Atoms, + energy, + identifier=None, + **arrays + ) -> None: + if "forces" in arrays and not self.has_array("forces"): + self.add_array("forces", shape=(3,), dtype=np.float64, per="element", + fill=np.nan) + if "stress" in arrays and not self.has_array("stress"): + # save stress in voigt notation + self.add_array("stress", shape=(6,), dtype=np.float64, per="chunk", + fill=np.nan) + super().add_structure(structure, identifier=identifier, energy=energy, + **arrays) - - def add_structure(self, structure, energy, identifier=None, forces=None, stress=None, **arrays): - data = {"energy": energy} - if forces is not None: - data["forces"] = forces - if stress is not None: - data["stress"] = stress - super().add_structure(structure, identifier, **data) - if self._table_cache: - self._table = self._table.append( - {"name": identifier, "atoms": structure, "energy": energy, "forces": forces, "stress": stress, - "number_of_atoms": len(structure)}, - ignore_index=True) - - def include_dataset(self, dataset): """ Add a pandas DataFrame to the saved structures. @@ -537,12 +478,26 @@ def include_dataset(self, dataset): - atoms(:class:`ase.Atoms`): the atomic structure - energy(float): energy of the whole structure - forces (Nx3 array of float): per atom forces, where N is the number of atoms in the structure + - charges (Nx3 array of floats): - stress (6 array of float): per structure stress in voigt notation """ - self._table_cache = self._table.append(dataset, ignore_index=True) - # in case given dataset has more columns than the necessary ones, swallow/ignore them in *_ - for name, atoms, energy, forces, stress, *_ in dataset.itertuples(index=False): - self.add_structure(atoms, name, energy=energy, forces=forces, stress=stress) + if ( + "name" not in dataset.columns + or "atoms" not in dataset.columns + or "energy" not in dataset.columns + ): + raise ValueError( + "At least columns 'name', 'atoms' and 'energy' must be present in dataset!" + ) + for row in dataset.itertuples(index=False): + kwargs = {} + if hasattr(row, "forces"): + kwargs["forces"] = row.forces + if hasattr(row, "stress"): + kwargs["stress"] = row.stress + self.add_structure( + row.atoms, energy=row.energy, identifier=row.name, **kwargs + ) def to_list(self, filter_function=None): """ @@ -554,12 +509,57 @@ def to_list(self, filter_function=None): Returns: tuple: list of structures, energies, forces, and the number of atoms """ - if filter_function is None: - data_table = self._table - else: - data_table = filter_function(self._table) + data_table = self.to_pandas() + if filter_function is not None: + data_table = filter_function(data_table) structure_list = data_table.atoms.to_list() energy_list = data_table.energy.to_list() + if "forces" not in data_table.columns: + raise ValueError("no forces defined in storage; call to_dict() instead.") force_list = data_table.forces.to_list() num_atoms_list = data_table.number_of_atoms.to_list() - return structure_list, energy_list, force_list, num_atoms_list + + return (structure_list, energy_list, force_list, num_atoms_list) + + def to_dict(self) -> Dict[str, Any]: + """Return a dictionary of all structures and training properties.""" + dict_arrays = {} + + # Get structure information. + dict_arrays['structure'] = list(self.iter_structures()) + + # Some arrays are only for internal usage or structure information that + # was already saved in dict['structure']. + internal_arrays = ['start_index', 'length', 'cell', 'pbc', 'positions', + 'symbols'] + for array in self.list_arrays(): + # Skip internal arrays. + if array in internal_arrays: + continue + + dict_arrays[array] = self.get_array_ragged(array) + return dict_arrays + + def iter(self, *arrays, wrap_atoms=True): + """ + Iterate over all structures in this object and all arrays that are defined + + Args: + wrap_atoms (bool): True if the atoms are to be wrapped back into the unit cell; passed to + :meth:`.get_structure()` + *arrays (str): name of arrays that should be iterated over + + Yields: + :class:`pyiron_atomistics.atomistitcs.structure.atoms.Atoms`, arrays: every structure attached to the object and queried arrays + """ + array_vals = (self.get_array_ragged(a) for a in arrays) + yield from zip(self.iter_structures(), *array_vals) + + @property + def plot(self): + """ + :class:`.TrainingPlots`: plotting interface + """ + if self._plots is None: + self._plots = TrainingPlots(self) + return self._plots diff --git a/pyiron_contrib/atomistics/fitsnap/__init__.py b/pyiron_contrib/atomistics/fitsnap/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pyiron_contrib/atomistics/meamfit/meamfit.py b/pyiron_contrib/atomistics/meamfit/meamfit.py new file mode 100644 index 000000000..1732b9b4f --- /dev/null +++ b/pyiron_contrib/atomistics/meamfit/meamfit.py @@ -0,0 +1,337 @@ +# coding: utf-8 +# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department +# Distributed under the terms of "New BSD License", see the LICENSE file. + +from __future__ import print_function + +import numpy as np +import os +import pandas as pd +import posixpath +import shutil +import random +from pyiron_base import GenericParameters, GenericJob, DataContainer + +__author__ = "Jan Janssen" +__copyright__ = "Copyright 2020, Max-Planck-Institut für Eisenforschung GmbH - " \ + "Computational Materials Design (CM) Department" +__version__ = "1.0" +__maintainer__ = "Jan Janssen" +__email__ = "janssen@mpie.de" +__status__ = "development" +__date__ = "Sep 1, 2017" + + +class MeamFit(GenericJob): + def __init__(self, project, job_name): + """ + Class to setup and run and MeamFit simulations. + + Examples: + Here is a simple example to setup and run a MeamFit job: + + >>> pr = Project('meamfit') + >>> job = pr.create.job.MeamFit(job_name='test_job') + >>> job.add_job_to_fitting(job_id=job_id) # job_id of the vasp MD job using for potential fitting + >>> job.run() + + Args: + project: Project object (defines path where job will be created and stored) + job_name: name of the job (must be unique within this project path) + + """ + super(MeamFit, self).__init__(project, job_name) + self.__version__ = None + self.__name__ = "MeamFit" + self.input = DataContainer({ + 'TYPE': 'EAM', + 'SEED': 'random', + 'CUTOFF_MAX': 5.0, + 'NTERMS': 3, + 'NTERMS_EMB': 3 + }, table_name='parameter') + self._executable_activate() + self._potential_performance_dataframe = pd.DataFrame({}) + self._potential_timings_dataframe = pd.DataFrame({}) + self._calculation_dataframe = pd.DataFrame({'Job id': [], 'Start config': [], 'End config': [], 'Step': [], + 'Quantity to fit': [], 'Weights': []}) + self._calculation_dataframe = self._calculation_dataframe.set_index('Job id') + + @property + def calculation_dataframe(self): + return self._calculation_dataframe + + @calculation_dataframe.setter + def calculation_dataframe(self, df): + self._calculation_dataframe = df + + @property + def potential_performance_dataframe(self): + return self._potential_performance_dataframe + + @property + def potential_timings_dataframe(self): + return self._potential_timings_dataframe + + @property + def potentials(self): + return list(self._potential_timings_dataframe.index) + + @property + def potential_paths(self): + return [posixpath.join(self.working_directory, pot) for pot in self.potentials] + + + @property + def random_seed(self): + incar = self.input['SEED'] + if incar == 'random': + self.input['SEED'] = random.randint(0, 100000) + incar = self.input['SEED'] + return incar + + @random_seed.setter + def random_seed(self, seed): + self.input['SEED'] = seed + + @property + def publication(self): + return { + "MeamFit": [ + { + "title": "MEAMfit: A reference-free modified embedded atom method (RF-MEAM) energy and force-fitting code", + "journal": "Computer Physics Communications", + "volume": "196", + "year": "2015", + "doi": "10.1016/j.cpc.2015.05.016", + "url": "https://doi.org/10.1016/j.cpc.2015.05.016", + "author": ["Andrew Ian Duff", "M.W. Finnis", "Philippe Maugis", "Barend J. Thijsse", "Marcel H.F. Sluiter"], + }, + ] + } + + + def set_input_to_read_only(self): + """ + This function enforces read-only mode for the input classes, but it has to be implement in the individual + classes. + """ + self.input.read_only = True + + def validate_ready_to_run(self): + if self._calculation_dataframe.empty: + raise ValueError("No training data added yet!") + + def write_input(self): + """ + Call routines that generate the codespecifc input files + + Returns: + + """ + if self.input['SEED'] == 'random': + self.input['SEED'] = random.randint(0, 100000) + self.input.write_file(file_name="settings", cwd=self.working_directory) + self._write_calc_db(calculation_dataframe=self._calculation_dataframe, + file_name="fitdbse", + cwd=self.working_directory) + self._copy_vasprun_xml(cwd=self.working_directory) + + def add_job_to_fitting(self, job_id, time_step_start=0, time_step_end=-1, time_step_delta=10, quantity='E0', + weight=[1.0, 0.0, 0.0]): + """ + Add output of VASP jobs to training data. + + Args: + job_id (int): job_id of the vasp MD job you want to use for potential fitting. + time_step_start (int): initial timestep - after equilibration + time_step_end (int): last timestep to use + time_step_delta (int): time step + quantity (str): the property in the vasprun file to be fit to, and can take the values ['E0', 'Free‐Energy', 'Force'] + ‘E0’: to the total energy (specifically the E0, sigma‐>0 value in the vasprun file); + ‘Free‐Energy’: will fit to the free energy (the F value in the vasprun file); + ‘Force’: will fit to atomic forces. + Note that only the first two letters are in fact read in by MEAMfit, so that it is sufficient to + write ‘Fr’ or ‘Fo’ for the second and third cases respectively. + weight (list): default is [1.0, 0.0, 0.0]. + + Raises: + ValueError: if given job is a not a Vasp job + """ + job = self.project.load(job_id) + if job.__name__ != "Vasp": + raise ValueError("Training data must be from VASP jobs!") + if time_step_end == -1: + time_step_end = np.shape(self.project.inspect(int(job_id))['output/generic/cells'])[0]-1 + if int(job_id) in [int(job) for job in self._calculation_dataframe.index]: + self._calculation_dataframe.loc[job_id] = pd.Series({'Start config': time_step_start, + 'End config': time_step_end, + 'Step': time_step_delta, + 'Quantity to fit': quantity, + 'Weights': weight}) + else: + df_tmp = pd.DataFrame({'Job id': [int(job_id)], + 'Start config': [time_step_start], + 'End config': [time_step_end], + 'Step': [time_step_delta], + 'Quantity to fit': [quantity], + 'Weights': [weight]}) + self._calculation_dataframe = pd.concat([self._calculation_dataframe, df_tmp.set_index('Job id')]) + + def _copy_vasprun_xml(self, cwd=None): + for job_id in self._calculation_dataframe.index: + job = self.project.load(job_id) + job.decompress() + working_directory = self.project.get_job_working_directory(int(job_id)) + shutil.copyfile(posixpath.join(working_directory, 'vasprun.xml'), + posixpath.join(cwd, 'vasprun_' + str(int(job_id)) + '.xml')) + job.compress() + + @staticmethod + def _write_calc_db(calculation_dataframe, file_name="fitdbse", cwd=None): + if cwd is not None: + file_name = posixpath.join(cwd, file_name) + with open(file_name, 'w') as f: + f.write(str(len(calculation_dataframe)) + ' # Files | Configs to fit | Quantity to fit | Weights \n') + for entry in zip(['vasprun_' + str(int(job_id)) + '.xml' for job_id in calculation_dataframe.index], + [start + 1 for start in calculation_dataframe['Start config']], + [end+1 for end in calculation_dataframe['End config']], + list(calculation_dataframe['Step']), + list(calculation_dataframe['Quantity to fit']), + list(calculation_dataframe['Weights'])): + file, start_config, end_config, step, quantity, weight = entry + if isinstance(weight, list): + weight = str(weight[0]) + ' ' + str(weight[1]) + ' ' + str(weight[2]) + f.write(str(file) + ' ' + str(int(start_config)) + '-' + str(int(end_config)) + 's' + str(int(step)) + + ' ' + str(quantity) + ' ' + str(weight) + '\n') + + + # define routines that collect all output files + def collect_output(self): + potential_timings_df = self._collect_timings(file_name='bestoptfuncs', cwd=self.working_directory) + self._potential_performance_dataframe = self._collect_potential_performance(cwd=self.working_directory) + self._potential_timings_dataframe = self._calculate_std(potential_timings_df, + self._potential_performance_dataframe) + self.to_hdf() + + def collect_logfiles(self): + pass + + def from_directory(self, directory): + """ + Collect input and output of a finished MeamFit job from a directory and convert into pyiron hdf file. + Args: + directory: working directory of the job. + + """ + if not self.status.finished: + self.status.collect = True + self._import_directory = directory + self.input.read_input(posixpath.join(directory, 'settings')) + self._calculation_dataframe = self._read_calc_db(file_name="fitdbse", cwd=directory) + self.collect_output() + self.status.finished = True + else: + raise RuntimeError("Unable to import MEAMfit calculation into finished job. Needs to be `initialized`.") + + # define hdf5 input and output + def to_hdf(self, hdf=None, group_name=None): + super(MeamFit, self).to_hdf(hdf=hdf, group_name=group_name) + with self.project_hdf5.open("input") as hdf5_input: + self.input.to_hdf(hdf5_input) + hdf5_input['calculation'] = self._calculation_dataframe.reset_index().to_dict(orient='list') + with self.project_hdf5.open("output") as hdf5_output: + hdf5_output['performance'] = self._potential_performance_dataframe.to_dict(orient='list') + hdf5_output['timings'] = self._potential_timings_dataframe.reset_index().to_dict(orient='list') + + def from_hdf(self, hdf=None, group_name=None): + super(MeamFit, self).from_hdf(hdf=hdf, group_name=group_name) + with self.project_hdf5.open("input") as hdf5_input: + # backwards compatible loading of input + if hdf5_input["parameter/NAME"] == "GenericParameters": + gp = GenericParameters() + gp.from_hdf(hdf5_input["parameter"]) + self.input['TYPE'] = gp['TYPE'] + self.input['SEED'] = gp['SEED'] + self.input['CUTOFF_MAX'] = gp['CUTOFF_MAX'] + self.input['NTERMS'] = gp['NTERMS'] + self.input['NTERMS_EMB'] = gp['NTERMS_EMB'] + else: + self.input.from_hdf(hdf5_input) + self.input.from_hdf(hdf5_input) + self._calculation_dataframe = pd.DataFrame(hdf5_input['calculation']).set_index('Job id') + with self.project_hdf5.open("output") as hdf5_output: + self._potential_performance_dataframe = pd.DataFrame(hdf5_output['performance']) + self._potential_timings_dataframe = pd.DataFrame(hdf5_output['timings']) + if 'File' in self._potential_timings_dataframe.columns: + self._potential_timings_dataframe = self._potential_timings_dataframe.set_index('File') + + + @staticmethod + def _read_calc_db(file_name="fitdbse", cwd=None): + if cwd is not None: + file_name = posixpath.join(cwd, file_name) + with open(file_name, 'r') as f: + content = f.readlines() + names_lst, range_lst, quantity_lst, weight_lst = zip(*[[part for part in line.split(' ') if part != ''] + for line in content[1:]]) + start_config_lst, end_config_lst = zip(*[[int(step) for step in step_range.split('-')] + for step_range in range_lst]) + df = pd.DataFrame({'Files': names_lst, + 'Start config': start_config_lst, + 'End config': end_config_lst, + 'Quantity to fit': quantity_lst, + 'Weights': [float(weight.split('\n')[0]) for weight in weight_lst]}) + return df + + @staticmethod + def _collect_timings(file_name='bestoptfuncs', cwd=None): + if cwd is not None: + file_name = posixpath.join(cwd, file_name) + with open(file_name, 'r') as f: + content = f.readlines() + content_table_lst = [line.split() for line in content[1:-2]] + content_table_re_lst = list(zip(*content_table_lst)) + file_name_lst = [file for file in os.listdir(cwd) if 'alloy_' in file] + file_name_lst.sort(key=lambda x: int(x.split('_')[-1])) + df = pd.DataFrame({'File': file_name_lst, + 'Precision': [float(number.replace('D', 'E')) for number in content_table_re_lst[1]], + 'Time [h]': [int(number) for number in content_table_re_lst[3]], + 'Time [min]': [int(number) for number in content_table_re_lst[5]]}) + return df.set_index('File') + + @staticmethod + def _collect_potential_performance(cwd=None): + df = pd.DataFrame({'Potential': [], + 'Structure': [], + 'fitdata': [], + 'truedata': []}) + files_in_cwd_lst = sorted(os.listdir(cwd)) + for file in files_in_cwd_lst: + if 'datapnts_best' in file: + with open(posixpath.join(cwd, file), 'r') as f: + content = f.readlines() + data_points_lst = list(zip(*[line.split() for line in content[6:]])) + df_new = pd.DataFrame({'Potential': [pot_file for pot_file in files_in_cwd_lst if + 'alloy_' + str(file).split('_best')[-1] == + pot_file.split('.')[-1]] * len(data_points_lst[0]), + 'Structure': data_points_lst[0], + 'fitdata': [float(number) for number in data_points_lst[1]], + 'truedata': [float(number) for number in data_points_lst[2]]}) + df = pd.concat([df, df_new]) + return df + + @staticmethod + def _calculate_std(potential_timings_df, potential_performance_df): + if isinstance(potential_timings_df, pd.DataFrame): + index_lst = potential_timings_df.index.values + else: + raise TypeError('Unsupported type for potential_lst.') + std_lst = [np.std(potential_performance_df[potential_performance_df['Potential'] == ind]['fitdata'].values - + potential_performance_df[potential_performance_df['Potential'] == ind]['truedata'].values) + for ind in index_lst] + if 'Std' in potential_timings_df.columns: + potential_timings_df = potential_timings_df.drop(columns=['Std'], axis=1) + std_df = pd.DataFrame({'Std': std_lst}) + std_df = std_df.set_index(index_lst) + return pd.concat([potential_timings_df, std_df], axis=1) diff --git a/pyiron_contrib/atomistics/mean_field/cm_retreat_presentation.pdf b/pyiron_contrib/atomistics/mean_field/cm_retreat_presentation.pdf new file mode 100644 index 000000000..09a66b679 Binary files /dev/null and b/pyiron_contrib/atomistics/mean_field/cm_retreat_presentation.pdf differ diff --git a/pyiron_contrib/atomistics/mean_field/core/bond_analysis.py b/pyiron_contrib/atomistics/mean_field/core/bond_analysis.py new file mode 100644 index 000000000..e635dbb42 --- /dev/null +++ b/pyiron_contrib/atomistics/mean_field/core/bond_analysis.py @@ -0,0 +1,702 @@ +# coding: utf-8 +# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department +# Distributed under the terms of "New BSD License", see the LICENSE file. + +import numpy as np +from scipy import stats +from scipy.constants import physical_constants + +from pyiron_base.jobs.job.generic import GenericJob +from pyiron_base.storage.datacontainer import DataContainer +from pyiron_atomistics.atomistics.structure.atoms import Atoms + +from matplotlib.colors import LinearSegmentedColormap +import matplotlib.pyplot as plt + +KB = physical_constants['Boltzmann constant in eV/K'][0] +myc = {'r': (1.0, 0.0, 44. / 255.), 'b': (71. / 255., 0.0, 167. / 255.), 'o': (1.0, 180. / 255., 7. / 255.), + 'g': (0.0 / 255.0, 180. / 255., 7. / 255.)} + + +class _BAInput(DataContainer): + """ + Class to store input parameters for the Bond Analysis classes. + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._structure = None + self._n_shells = None + self._cutoff_radius = None + + @property + def structure(self) -> Atoms: + return self._structure + + @structure.setter + def structure(self, atoms: Atoms): + if not isinstance(atoms, Atoms): + raise TypeError(f'structure must be of type Atoms but got {type(atoms)}') + self._structure = atoms + + @property + def n_shells(self): + return self._n_shells + + @n_shells.setter + def n_shells(self, n): + if not isinstance(n, int): + raise TypeError(f"n_shells must be an integer but got {type(n)}") + self._n_shells = n + + @property + def cutoff_radius(self): + return self._cutoff_radius + + @cutoff_radius.setter + def cutoff_radius(self, r): + if not isinstance(r, (int, float)): + raise TypeError(f"n_shells must be an integer/float but got {type(r)}") + self._cutoff_radius = r + + +class _BondAnalysisParent(GenericJob): + def __init__(self, project, job_name): + super(_BondAnalysisParent, self).__init__(project, job_name) + self._python_only_job = True + self.input = _BAInput(table_name="job_input") + self.output = DataContainer(table_name="job_output") + self._nn = None + self._n_bonds_per_shell = None + self._all_nn_bond_vectors = None + self.histogram = _Histograms() + + def validate_ready_to_run(self): + """ + Check if necessary inputs are provided, and everything is in order for the computation to run. + """ + if self.input.structure is None: + raise AttributeError('.input.structure must be set') + if (self.input.n_shells is None) and (self.input.cutoff_radius is None): + raise AttributeError('either .input.n_shells or .input.cutoff_radius must be set') + + def to_hdf(self, hdf=None, group_name=None): + """ + Store the StructureToBonds object in the HDF5 File. + + Args: + hdf (ProjectHDFio): HDF5 group object - optional + group_name (str): HDF5 subgroup name - optional + """ + super(_BondAnalysisParent, self).to_hdf() + self.input.to_hdf(self.project_hdf5) + self.output.to_hdf(self.project_hdf5) + + def from_hdf(self, hdf=None, group_name=None): + """ + Restore the StructureToBonds object from the HDF5 File. + + Args: + hdf (ProjectHDFio): HDF5 group object - optional + group_name (str): HDF5 subgroup name - optional + """ + super(_BondAnalysisParent, self).from_hdf() + self.input.from_hdf(self.project_hdf5) + self.output.from_hdf(self.project_hdf5) + + +class StaticBondAnalysis(_BondAnalysisParent): + """ + A job that analyzes the bond relations in a minimized structure. + """ + + def __init__(self, project, job_name): + super(StaticBondAnalysis, self).__init__(project, job_name) + + @staticmethod + def _set_cutoff_radius(structure, n_shells, cutoff_radius=None, eps=1e-5): + """ + If cutoff radius is not specified as an input, set it based off of the n_shells. + Args: + structure: + n_shells: + cutoff_radius: + eps: + + Returns: + cutoff_radius + """ + if cutoff_radius is None: + nn = structure.get_neighbors(num_neighbors=1000) + all_shells = nn.shells[0] + needed_dists = len(all_shells[all_shells < n_shells + 1]) + cutoff_radius = nn.distances[0][:needed_dists][-1] + eps + return cutoff_radius + + @staticmethod + def _get_nn(structure, cutoff_radius): + """ + Return the neighbors object. + Args: + structure: + cutoff_radius: + + Returns: + neighbors + n_bonds_per_shell + """ + neighbors = structure.get_neighbors(num_neighbors=None, cutoff_radius=cutoff_radius) + nn_distances = np.around(neighbors.distances, decimals=5) + _, _, n_bonds_per_shell = np.unique(np.around(nn_distances[0], decimals=5), return_inverse=True, + return_counts=True) + return neighbors, n_bonds_per_shell + + @staticmethod + def _set_n_shells(nn_distances, n_shells=None): + """ + If n_shells is not specified as an input, set it based off of the cutoff radius. + Args: + nn_distances: + n_shells: + + Returns: + n_shells + """ + if n_shells is None: + nn_dists = np.around(nn_distances[0], decimals=5) + n_shells = int(np.unique(nn_dists, return_index=True)[1][-1] + 1) + return n_shells + + @staticmethod + def _populate_shells(n_bonds_per_shell, vectors, indices): + """ + Arrange data according to shells. + Args: + n_bonds_per_shell: + vectors: + indices: + + Returns: + vectors_per_shell + """ + vectors_per_shell = [] + sums = 0 + for n in n_bonds_per_shell: + sorted_indices = np.argsort(indices[sums:sums + n]) + vectors_per_shell.append(vectors[sums:sums + n][sorted_indices]) + sums += n + return vectors_per_shell + + @staticmethod + def _rotation_matrix_from_vectors(vec_1, vec_2): + """ + Find the rotation matrix that aligns vec_1 to vec_2. + Args: + vec_1: A 3d "source" vector + vec_2: A 3d "destination" vector + + Returns: + A transformation matrix (3x3) which when applied to vec1, aligns it with vec2. + """ + a, b = (vec_1 / np.linalg.norm(vec_1)).reshape(3), (vec_2 / np.linalg.norm(vec_2)).reshape(3) + v = np.cross(a, b) + c = np.dot(a, b) + if np.any(v): # if not all zeros then + s = np.linalg.norm(v) + kmat = np.array([[0, -v[2], v[1]], [v[2], 0, -v[0]], [-v[1], v[0], 0]]) + return np.eye(3) + kmat + kmat.dot(kmat) * ((1 - c) / (s ** 2)) + elif not np.any(v) and c < 0.: + return np.eye(3) * -1 # for opposite directions + else: + return np.eye(3) # for identical directions + + def _get_irreducible_bond_vector_per_shell(self, nn, n_bonds_per_shell): + """ + Get one irreducible bond vector per nearest neighbor shell. If symmetries are known, this irreducible bond can + be used to generate all other bonds for the corresponding shell. + """ + # arrange bond vectors according to their shells + bond_vectors_per_shell = self._populate_shells(vectors=np.around(nn.vecs[0], decimals=5), + indices=nn.indices[0], + n_bonds_per_shell=n_bonds_per_shell) + # save 1 irreducible bond vector per shell + per_shell_irreducible_bond_vectors = np.array([b[0] for b in bond_vectors_per_shell]) + # get all the rotations from spglib + data = self.input.structure.get_symmetry() + # account for only 0 translation rotations + all_rotations = data['rotations'][np.argwhere(data['translations'][:48].sum(-1) < 1e-10).flatten()] + # and sort them doing the following: + per_shell_0K_rotations = [] + all_nn_bond_vectors_list = [] + for b in enumerate(per_shell_irreducible_bond_vectors): + # get 'unique bonds' from the spglib data + unique_bonds = np.unique(np.dot(b[1], all_rotations), axis=0) + args_list = [] + for bond in unique_bonds: + # collect the arguments of the rotations that that give the unique bonds + args = [] + for i, bd in enumerate(np.dot(b[1], all_rotations)): + if np.array_equal(np.round(bd, decimals=5), np.round(bond, decimals=5)): + args.append(i) + args_list.append(args[0]) + # sort the arguments and append the rotations to a list... + per_shell_0K_rotations.append(all_rotations[np.sort(args_list)]) + # and the unique bonds also, to another list... + all_nn_bond_vectors_list.append(np.dot(b[1], per_shell_0K_rotations[-1])) + # and clump the unique bonds into a single array + all_nn_bond_vectors_list = np.array([bonds for per_shell_bonds in all_nn_bond_vectors_list + for bonds in per_shell_bonds]) + return per_shell_irreducible_bond_vectors, per_shell_0K_rotations, all_nn_bond_vectors_list + + def _get_transformations(self, n_bonds_per_shell, all_nn_bond_vectors): + """ + Get the transformation matrices that take the bond vectors of each shell from cartesian [x, y, z] axes to + [longitudinal, transverse1 and transverse2] axes. + Args: + n_bonds_per_shell: + all_nn_bond_vectors: + + Returns: + per_shell_transformations + """ + sums = 0 + per_shell_transformation_matrices = [] + for i in n_bonds_per_shell: + bonds = all_nn_bond_vectors[sums:sums + i].copy() + sums += i + # normalize the bonds + bonds /= np.linalg.norm(bonds, axis=1)[:, np.newaxis] + transformation_matrices = [] + for b in bonds: + b1 = b.copy() # first bond is the longitudinal bond + # second bond is normal to the first (transverse1). If multiple normal bonds, select 1 + b2 = bonds[np.argwhere(np.round(bonds@b1, decimals=5) == 0.).flatten()[0]] + # third bond is then normal to both the first and second bonds (transverse2) + b3 = np.cross(b1, b2) + if b1.dot(np.cross(b2, b3)) < 0.: # if this condition is not met + b2, b3 = b3, b2 # reverse b2 and b3 + transformation_matrices.append(np.array([b1, b2, b3])) + per_shell_transformation_matrices.append(transformation_matrices) + return per_shell_transformation_matrices + + @staticmethod + def _get_bond_indexed_neighbor_list(nn, n_bonds_per_shell, all_nn_bond_vectors, structure): + """ + For each atom with index i, obtain the index of another atom M[i][j], + the difference between which gives bond vector index j. + + If M_ij is the matrix, i belongs to [1,N], j belongs to [1,m], where, + i = index of atom 1 + M_ij = index of atom 2 + j = index of the unique bond vector between atoms 1 and 2 + N = number of atoms + m = number of unique bonds + Args: + nn: + n_bonds_per_shell: + all_nn_bond_vectors: + structure: + + Returns: + per_shell_bond_indexed_neighbor_list + """ + nn_vecs = np.around(nn.vecs, decimals=5) + nn_indices = nn.indices + sums = 0 + per_shell_bond_indexed_neighbor_list = [] + for n in n_bonds_per_shell: + nn_vecs_per_shell = nn_vecs[:, sums:sums + n].copy() + nn_indices_per_shell = nn_indices[:, sums:sums + n].copy() + bond_vectors_list = all_nn_bond_vectors[sums:sums + n].copy() + sums += n + # initialize the M_ij matrix with shape [atoms x unique_bonds] + x_0 = np.around(structure.positions, decimals=5) # zero Kelvin positions + M_ij = np.zeros([len(x_0), len(nn_vecs_per_shell[0])]).astype(int) + # populate the M_ij matrix + for i, per_atom_nn in enumerate(nn_vecs_per_shell): + for vec, ind in zip(per_atom_nn, nn_indices_per_shell[i]): + try: + j = np.argwhere(np.all(np.isclose(vec, bond_vectors_list), axis=1))[0, 0] + except IndexError: # this is an exception for HCP! + j = np.argwhere(np.all(np.isclose(-vec, bond_vectors_list), axis=1))[0, 0] + M_ij[i][j] = ind + per_shell_bond_indexed_neighbor_list.append(M_ij) + return per_shell_bond_indexed_neighbor_list + + @staticmethod + def _get_bond_relations_list(per_shell_bond_indexed_neighbor_list): + """ + Use the per_shell_bond_indexed_neighbor_list for each shell to generate a 'bond relations' list of the form [[i, m_ij, j], ...] + connecting every atom (with index i) in the structure to its nearest neighbor atom/s (with index m_ij), + giving a bond vector/s, which can be transformed to the direction of the irreducible bond vector of that + shell using a symmetry operation (with index j). + Args: + per_shell_bond_indexed_neighbor_list: + + Returns: + per_shell_bond_relations + """ + per_shell_bond_relations = [] + for M_ij in per_shell_bond_indexed_neighbor_list: # enumerate over shells + per_shell = [] + for j, row in enumerate(M_ij.T): # enumerate over bonds + per_bond = [] + for i, m_ij in enumerate(row): # enumerate over atoms + per_bond.append([i+1, m_ij+1, j+1]) # atom_1_index, atom_2_index, symmetry_op_index + per_shell.append(per_bond) + per_shell_bond_relations.append(np.array(per_shell)) + return per_shell_bond_relations + + def analyze_bonds(self): + # set cutoff radius, if not already set + self.input.cutoff_radius = self._set_cutoff_radius(structure=self.input.structure, + n_shells=self.input.n_shells, + cutoff_radius=self.input.cutoff_radius, + eps=1e-5) + # run get_neighbors + self._nn, self._n_bonds_per_shell = self._get_nn(structure=self.input.structure, + cutoff_radius=self.input.cutoff_radius) + # if n_shells is not set in the input, make sure it is now + self.input.n_shells = self._set_n_shells(nn_distances=self._nn.distances, + n_shells=self.input.n_shells) + + # get irreducible bond vectors and 0K rotations + irr_bvs = self._get_irreducible_bond_vector_per_shell(nn=self._nn, + n_bonds_per_shell=self._n_bonds_per_shell) + self.output.per_shell_irreducible_bond_vectors, self.output.per_shell_0K_rotations, \ + self._all_nn_bond_vectors = irr_bvs + # get transformations + self.output.per_shell_transformation_matrices = self._get_transformations( + n_bonds_per_shell=self._n_bonds_per_shell, + all_nn_bond_vectors=self._all_nn_bond_vectors) + # get per_shell_bond_indexed_neighbor_list + self.output.per_shell_bond_indexed_neighbor_list = \ + self._get_bond_indexed_neighbor_list(nn=self._nn, n_bonds_per_shell=self._n_bonds_per_shell, + all_nn_bond_vectors=self._all_nn_bond_vectors, + structure=self.input.structure) + # get bond relations + self.output.per_shell_bond_relations = \ + self._get_bond_relations_list(per_shell_bond_indexed_neighbor_list= + self.output.per_shell_bond_indexed_neighbor_list) + + def run_static(self): + self.analyze_bonds() + self.to_hdf() + + +class MDBondAnalysis(_BondAnalysisParent): + """ + A job which, given an MD trajectory, gives 'bond data' based off of the bond relations of the minimized structure. + """ + + def __init__(self, project, job_name): + super(MDBondAnalysis, self).__init__(project, job_name) + self.input.md_job = None + self.input.thermalize_snapshots = 20 + self.input.md_trajectory = None + self.input.md_cells = None + self._structure = None + self._md_trajectory = None + self._md_cells = None + + @staticmethod + def _find_mic(cell, vectors, pbc=[True, True, True]): + """ + Find vectors following minimum image convention (mic). + cell: The cell in reference to which the vectors are expressed. + vectors (list/numpy.ndarray): 3d vector or a list/array of 3d vectors. + pbc: Periodic bondary condition along each coordinate axis. + Returns: numpy.ndarray of the same shape as input with mic + """ + vecs = np.asarray(vectors).reshape(-1, 3) + if any(pbc): + vecs = np.einsum('ji,nj->ni', np.linalg.inv(cell), vecs) + vecs[:, pbc] -= np.rint(vecs)[:, pbc] + vecs = np.einsum('ji,nj->ni', cell, vecs) + return vecs.reshape(np.asarray(vectors).shape) + + def validate_ready_to_run(self): + """ + Check if necessary inputs are provided, and everything is in order for the computation to run. + """ + if self.input.md_job is not None: + self._md_trajectory = self.input.md_job.output.unwrapped_positions[self.input.thermalize_snapshots:] + self._md_cells = self.input.md_job.output.cells[self.input.thermalize_snapshots:] + elif self.input.md_trajectory is not None: + self._md_trajectory = self.input.md_trajectory + if self.input.md_cells is None: + self._md_cells = np.array([self.input.structure.cell.array]*len(self._md_trajectory)) + else: + raise AttributeError('Either .input.md_job or .input.md_trajectory must be set') + + static = StaticBondAnalysis(project=self.project_hdf5, job_name=self.job_name + '_static') + static.input = self.input + static.analyze_bonds() + self.output = static.output + + def _get_xyz_bond_vectors(self, per_atom=False, return_out=False): + """ + Use the 'bond relations' list to obtain the MD bond vectors for each shell. + """ + self.output.per_shell_xyz_bond_vectors = [] + for i, br_per_s in enumerate(self.output.per_shell_bond_relations): + per_shell = [] + for bond in br_per_s: + bond_vectors = self._md_trajectory[:, bond[:, 1] - 1] - self._md_trajectory[:, bond[:, 0] - 1] + bond_vectors_mic = [] + for j, snapshot in enumerate(bond_vectors): + bond_vectors_mic.append(self._find_mic(self._md_cells[j], snapshot)) + if per_atom: + per_shell.append(np.array(bond_vectors_mic)) + else: + per_shell.append(np.array(bond_vectors_mic).reshape(-1, 3)) + self.output.per_shell_xyz_bond_vectors.append(np.array(per_shell)) + if return_out: + return self.output.per_shell_xyz_bond_vectors + + def _get_long_t1_t2_bond_vectors(self): + """ + Convert the MD bond vectors from cartesian [x, y, z] axes to [longitudinal, transverse1 and transverse2] axes, + using the transformation matrices for each bond in each shell. + """ + self.output.per_shell_long_t1_t2_bond_vectors = [] + for shell in np.arange(self.input.n_shells): + per_shell = [] + for i, transform in enumerate(self.output.per_shell_transformation_matrices[shell]): + bond_vectors = self.output.per_shell_xyz_bond_vectors[shell][i] + transformed_vectors = np.dot(bond_vectors, transform.T) + transformed_vectors[:, 0] = np.linalg.norm(bond_vectors, axis=-1) + # the next 4 lines are an exception for HCP! + if np.any(transformed_vectors[:, 0] < 0.): + for j, vec in enumerate(transformed_vectors): + if vec[0] < 0.: + transformed_vectors[j] = -vec + per_shell.append(transformed_vectors) + self.output.per_shell_long_t1_t2_bond_vectors.append(np.array(per_shell)) + + @staticmethod + def _cartesian_to_cylindrical(vector): + """ + Helper method for get_md_cylindrical_long_t1_t2. + """ + if len(vector.shape) == 1: + vector = np.array([vector]) + r = np.linalg.norm(vector[:, -2:], axis=1) + phi = np.arctan2(vector[:, 2], vector[:, 1]) + return np.array([vector[:, 0], r, phi]).T + + def _get_long_r_phi_bond_vectors(self): + """ + Convert the [longitudinal, transverse1 and transverse2] which are cartesian axes to cylindrical axes + [longitudinal, r and phi]. + """ + self.output.per_shell_long_r_phi_bond_vectors = [] + for shell in self.output.per_shell_long_t1_t2_bond_vectors: + per_bond = [] + for bond in shell: + per_bond.append(self._cartesian_to_cylindrical(bond)) + self.output.per_shell_long_r_phi_bond_vectors.append(np.array(per_bond)) + + def run_static(self): + self._get_xyz_bond_vectors() + self._get_long_t1_t2_bond_vectors() + #self._get_long_r_phi_bond_vectors() + self.to_hdf() + + def get_1d_histogram_xyz(self, shell=0, bond=None, n_bins=20, d_range=None, density=True, axis=0, + moment=True): + return self.histogram.get_per_shell_1d_histogram(self.output.per_shell_xyz_bond_vectors, shell=shell, bond=bond, + n_bins=n_bins, d_range=d_range, density=density, axis=axis, + moment=moment) + + def get_1d_histogram_long_t1_t2(self, shell=0, bond=None, n_bins=20, d_range=None, density=True, axis=0, + moment=True): + return self.histogram.get_per_shell_1d_histogram(self.output.per_shell_long_t1_t2_bond_vectors, shell=shell, + bond=bond, n_bins=n_bins, d_range=d_range, density=density, + axis=axis, moment=moment) + + def get_1d_histogram_long_r_phi(self, shell=0, bond=None, n_bins=20, d_range=None, density=True, axis=0, + moment=True): + return self.histogram.get_per_shell_1d_histogram(self.output.per_shell_long_r_phi_bond_vectors, shell=shell, + bond=bond, n_bins=n_bins, d_range=d_range, density=density, + axis=axis, moment=moment) + + def get_3d_histogram_xyz(self, shell=0, bond=None, n_bins=20, d_range=None, density=True): + return self.histogram.get_per_shell_3d_histogram(self.output.per_shell_xyz_bond_vectors, supp_data=None, shell=shell, bond=bond, + n_bins=n_bins, d_range=d_range, density=density) + + def get_3d_histogram_long_t1_t2(self, shell=0, bond=None, n_bins=20, d_range=None, density=True): + return self.histogram.get_per_shell_3d_histogram(self.output.per_shell_long_t1_t2_bond_vectors, + supp_data=self.output.per_shell_xyz_bond_vectors, shell=shell, + bond=bond, n_bins=n_bins, d_range=d_range, density=density) + + def get_3d_histogram_long_r_phi(self, shell=0, bond=None, n_bins=20, d_range=None, density=True): + return self.histogram.get_per_shell_3d_histogram(self.output.per_shell_long_r_phi_bond_vectors, shell=shell, supp_data=None, + bond=bond, n_bins=n_bins, d_range=d_range, density=density) + + def get_potential_long_r_phi(self, temperature=300., shell=0, bond=None, n_bins=20, d_range=None, density=True): + pd, bins = self.get_3d_histogram_long_r_phi(shell=shell, bond=bond, n_bins=n_bins, d_range=d_range, + density=density) + r_bins = bins[1][0, :, 0] + delta_r = (r_bins[1] - r_bins[0]) / 2 + mean_over_phi_pd = pd.mean(axis=-1) + # since in cylindrical coordinates, the pd of r needs to be divided by the bins, + mean_over_phi_pd /= np.outer(np.ones(n_bins), r_bins + delta_r) + mean_over_phi_pd /= mean_over_phi_pd.sum() + potential = -KB * temperature * np.log(mean_over_phi_pd + 1e-10) + potential = potential.T + return potential - potential.min(), np.meshgrid(bins[0][:, 0, 0], bins[1][0, :, 0]) + + def get_potential_long_t1_t2(self, temperature=300., shell=0, bond=None, n_bins=20, d_range=None, density=True, + mean_over_final_axis=False): + pd, bins = self.get_3d_histogram_long_t1_t2(shell=shell, bond=bond, n_bins=n_bins, d_range=d_range, + density=density) + if mean_over_final_axis: + pd = pd.mean(axis=-1) + pd /= pd.sum() + potential = -KB * temperature * np.log(pd + 1e-10) + if mean_over_final_axis: + return (potential - potential.min()).T, np.meshgrid(bins[0][:, 0, 0], bins[1][0, :, 0]) + else: + return potential - potential.min(), bins + + + @staticmethod + def _get_rho_corr_and_uncorr(x_data, y_data, n_bins=101): + rho_corr, x_edges, y_edges = np.histogram2d(x_data, y_data, (n_bins, n_bins), density=True) + rho_uncorr = np.outer(rho_corr.sum(axis=1), rho_corr.sum(axis=0)) + return rho_corr / rho_corr.sum(), rho_uncorr / rho_uncorr.sum(), x_edges, y_edges + + @staticmethod + def _get_corr_column_value(axis): + if axis == 'long': + return 0 + elif axis == 't1': + return 1 + elif axis == 't2': + return 2 + else: + raise ValueError("choose between long, t1, and t2") + + def _get_correlations(self, shell, bond_x, bond_y, axis_x, axis_y, n_bins): + all_bonds = self.output.per_shell_long_t1_t2_bond_vectors[shell] + n_bonds = len(all_bonds) + if (bond_x >= n_bonds) or (bond_y >= n_bonds): + raise ValueError("there are only {} bonds in shell {}".format(n_bonds, shell)) + axis_0 = self._get_corr_column_value(axis_x) + axis_1 = self._get_corr_column_value(axis_y) + return self._get_rho_corr_and_uncorr(x_data=all_bonds[bond_y, :, axis_0], y_data=all_bonds[bond_x, :, axis_1], + n_bins=n_bins) + + def get_mutual_information(self, shell=0, bond_x=0, bond_y=0, axis_x='long', axis_y='long', n_bins=101): + rho_corr, rho_uncorr, _, _ = self._get_correlations(shell=shell, bond_x=bond_x, bond_y=bond_y, axis_x=axis_x, + axis_y=axis_y, n_bins=n_bins) + sel = (rho_uncorr > 0.0) * (rho_corr > 0.0) + return -(np.log((rho_corr[sel]) / rho_uncorr[sel]) * rho_uncorr[sel]).sum() + + def plot_correlations(self, shell=0, bond_x=0, bond_y=0, axis_x='long', axis_y='long', n_bins=101): + rho_corr, rho_uncorr, x_edges, y_edges = self._get_correlations(shell=shell, bond_x=bond_x, + bond_y=bond_y, axis_x=axis_x, + axis_y=axis_y, n_bins=n_bins) + rho_diff = rho_corr - rho_uncorr + cm = LinearSegmentedColormap.from_list('my_spec', [myc['b'], (1, 1, 1), myc['o']], N=n_bins) + plims = x_edges.min(), x_edges.max(), y_edges.min(), y_edges.max() # plot limits + plt.imshow(rho_diff[::-1, :], cmap=cm, extent=plims) + plt.xticks(np.around(np.linspace(plims[0], plims[1], 5), decimals=2)) + plt.yticks(np.around(np.linspace(plims[2], plims[3], 5), decimals=2)) + plt.xlabel(axis_x + '_' + str(bond_x)) + plt.ylabel(axis_y + '_' + str(bond_y)) + plt.show() + + +class _Histograms(object): + """ + A helper class which gives histograms of the input 'bond_representation', which is bond data of a single bond in a + shell or all the bonds in the shell, represented in either [x, y, z] coordinates, [longitudinal, transverse1, + transverse2] coordinates or [longitudinal, r, phi] coordinates. + """ + + @staticmethod + def _get_bin_centers(bins): + if len(bins) == 3: + x_bins, y_bins, z_bins = bins + return [(x_bins[1:] + x_bins[:-1]) / 2, (y_bins[1:] + y_bins[:-1]) / 2, (z_bins[1:] + z_bins[:-1]) / 2] + else: + return (bins[1:] + bins[:-1]) / 2 + + @staticmethod + def _check_histogram_bonds(required_data, bond, shell): + """ + Helper method to get_per_shell_3d_histogram and get_per_shell_1d_histogram + Args: + required_data: + bond: + shell: + + Returns: + required_data + """ + if bond is None: + required_data = required_data.reshape(-1, 3) # flatten over all bonds in the shell + else: + n_bonds = len(required_data) + if bond >= n_bonds: + raise ValueError("there are only {} bonds in shell {}".format(n_bonds, shell)) + required_data = required_data[bond] + return required_data + + def get_per_shell_1d_histogram(self, bond_representation, shell=0, bond=None, n_bins=20, d_range=None, + density=True, axis=0, moment=True): + """ + Get the 1d-histograms of data: + Args: + bond_representation: Histogram of which data to be obtained. 'bond_vectors' or 'long_t1_t2' or 'cyl_long_t1_t2' + (Default is 'bond_vectors') + shell: The shell (Default is 0, the 0th shell) + bond: The bond whose 3D histogram is to be obtained (Default is None, flatten over all the bonds) + n_bins: Number of bins along each axis (Default is 20 bins) + d_range: The range of the bins along each axis (Default is None, set range to the min/max of the bins) + density: If True, returns the probability densities, else returns counts (Default is True) + axis: Along which axis to get the histogram (Default is 0, the 0th axis) + moment: If True, returns the moments 1-4 of the distribution (Default is True) + + Returns: + rho (bins): The counts/probability densities + centered_bins (list[x-bins, y-bins, z-bins]): The center values of the bins + """ + if axis not in [0, 1, 2, -1, -2, -3]: + raise ValueError("axis can only take values 0, 1, 2, -1, -2, -3") + required_data = bond_representation[shell] + required_data = self._check_histogram_bonds(required_data=required_data, bond=bond, shell=shell) + rho, bins = np.histogram(required_data[:, axis], bins=n_bins, range=d_range, density=density) + if moment: + moments = [np.mean(required_data[:, axis])] + moments += [stats.moment(a=required_data[:, axis], moment=i) for i in np.arange(2, 5)] + return rho/rho.sum(), self._get_bin_centers(bins=bins), moments + else: + return rho/rho.sum(), self._get_bin_centers(bins=bins) + + def get_per_shell_3d_histogram(self, bond_representation, supp_data=None, shell=0, bond=None, n_bins=20, d_range=True, + density=True): + """ + Get the 3d-histograms of data: + Args: + bond_representation: Histogram of which data to be obtained. 'bond_vectors' or 'long_t1_t2' or 'cyl_long_t1_t2' + (Default is 'bond_vectors') + shell: The shell (Default is 0, the 0th shell) + bond: The bond whose 3D histogram is to be obtained (Default is None, flatten over all the bonds) + n_bins: Number of bins along each axis (Default is 20 bins) + d_range: The range of the bins along each axis (Default is None, set range to the min/max of the bins) + density: If True, returns the probability densities, else returns counts (Default is True) + + Returns: + rho (bins x bins x bins): The counts/probability densities + centered_bins (list[x-bins, y-bins, z-bins]): The center values of the bins + """ + required_data = bond_representation[shell] + required_data = self._check_histogram_bonds(required_data=required_data, bond=bond, shell=shell) + rho, bins = np.histogramdd(required_data, bins=n_bins, density=density) + rho /= rho.sum() + bins = self._get_bin_centers(bins=bins) + bins_3d = np.meshgrid(bins[0], bins[1], bins[2], indexing='ij') + return rho, bins_3d diff --git a/pyiron_contrib/atomistics/mean_field/morse_al_md.ipynb b/pyiron_contrib/atomistics/mean_field/morse_al_md.ipynb new file mode 100644 index 000000000..39f96abc1 --- /dev/null +++ b/pyiron_contrib/atomistics/mean_field/morse_al_md.ipynb @@ -0,0 +1,289 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "a73bce6e", + "metadata": { + "ExecuteTime": { + "end_time": "2022-09-26T13:20:48.229238Z", + "start_time": "2022-09-26T13:20:44.058044Z" + } + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "\n", + "from os.path import abspath, join, isfile\n", + "from os import remove\n", + "from shutil import rmtree\n", + "from glob import glob\n", + "\n", + "from pyiron_atomistics import Project\n", + "from pyiron_contrib.atomistics.mean_field.core.bond_analysis import StaticBondAnalysis\n", + "\n", + "def cleanup_job(job):\n", + " \"\"\"\n", + " Removes all the child jobs (files AND folders) to save disk space and reduce file count, and only keeps\n", + " the hdf file.\n", + " \"\"\"\n", + " for f in glob(abspath(join(job.working_directory, '../..')) + '/' + job.job_name + '_*'):\n", + " if isfile(f):\n", + " remove(f)\n", + " else:\n", + " rmtree(f)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7bd29db7", + "metadata": { + "ExecuteTime": { + "end_time": "2022-09-26T13:20:52.734516Z", + "start_time": "2022-09-26T13:20:48.233296Z" + } + }, + "outputs": [], + "source": [ + "alpha=1.5\n", + "pr = Project('morse_al/md_runs_nvt/alpha_' + str(alpha).replace('.', '_'))\n", + "# pr.remove_jobs(recursive=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0d0bea2d", + "metadata": { + "ExecuteTime": { + "end_time": "2022-09-26T13:20:53.479854Z", + "start_time": "2022-09-26T13:20:53.475012Z" + } + }, + "outputs": [], + "source": [ + "# potential functions\n", + "D = 0.1\n", + "a_0 = 2.856\n", + "kappa = 0.\n", + "\n", + "# for lammps\n", + "def md_morse(D=D, alpha=alpha, r_0=a_0, b=1):\n", + " config = 'atom_style bond\\nbond_style morse\\n'\n", + " for i in range(b):\n", + " vals = (i+1, D, alpha, a_0)\n", + " config += 'bond_coeff %d %.7f %.7f %.7f\\n'%(vals)\n", + " return pd.DataFrame({'Name': ['Morse'],\n", + " 'Filename': [[]], \n", + " 'Model' : ['Morse'], \n", + " 'Species' : [['Al']], \n", + " 'Config' : [[config]]})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "587e4da6", + "metadata": { + "ExecuteTime": { + "end_time": "2022-09-26T13:20:54.061220Z", + "start_time": "2022-09-26T13:20:54.057138Z" + } + }, + "outputs": [], + "source": [ + "# standard stuff\n", + "\n", + "element = 'Al'\n", + "supercell_size = 4\n", + "n_atoms = 4*supercell_size**3\n", + "samples = 5\n", + "md_steps = 1e4\n", + "md_samples = md_steps / 2000\n", + "temperatures = np.linspace(100, 900, 9)\n", + "base_structure = pr.create.structure.bulk(name=element, cubic=True).repeat(supercell_size)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9300f452", + "metadata": { + "ExecuteTime": { + "end_time": "2022-09-26T13:21:01.120643Z", + "start_time": "2022-09-26T13:20:59.785630Z" + } + }, + "outputs": [], + "source": [ + "# relax the structure to atm pressure\n", + "\n", + "minim_job = pr.create.job.Lammps('minim_job', delete_existing_job=True)\n", + "minim_job.structure = base_structure\n", + "minim_job.potential = md_morse()\n", + "minim_job.calc_minimize(pressure=0.0001)\n", + "minim_job.run()\n", + "structure = minim_job.get_structure()\n", + "a_0 = (structure.cell/supercell_size/np.sqrt(2))[0][0]\n", + "U_0 = minim_job.output.energy_pot[-1]/n_atoms" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52748d3b", + "metadata": { + "ExecuteTime": { + "end_time": "2022-09-26T13:21:02.060511Z", + "start_time": "2022-09-26T13:21:01.508450Z" + } + }, + "outputs": [], + "source": [ + "# analyze bonds and get rotations and displacement matrix\n", + "\n", + "stat_ba = pr.create_job(StaticBondAnalysis, 'stat_ba', delete_existing_job=True)\n", + "stat_ba.input.structure = structure.copy()\n", + "stat_ba.input.n_shells = 1\n", + "stat_ba.run()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6f6ff28b-77f4-4dbd-bb65-11ac339758e9", + "metadata": { + "ExecuteTime": { + "end_time": "2022-09-26T13:21:02.332021Z", + "start_time": "2022-09-26T13:21:02.328904Z" + } + }, + "outputs": [], + "source": [ + "# from the static bond analysis, create a bonds list that can be passed to a pyiron lammps job\n", + "\n", + "def get_bonds_list(bond_relations):\n", + " # for FCC, only include 6 bonds out of 12, as other 6 are anti-parallel\n", + " bonds_list = bond_relations[::2]\n", + " for (per_bond_relations, i) in zip(bonds_list, np.arange(len(bonds_list))+1):\n", + " # change bond type index\n", + " per_bond_relations[:, 2] = i\n", + " return bonds_list.reshape(-1, 3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a73a0ec1", + "metadata": {}, + "outputs": [], + "source": [ + "## Run NVT job first and then run NVE" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e59068ed", + "metadata": { + "ExecuteTime": { + "end_time": "2022-09-26T13:21:21.131093Z", + "start_time": "2022-09-26T13:21:10.725370Z" + }, + "scrolled": true + }, + "outputs": [], + "source": [ + "## NVT\n", + "\n", + "# for i, temp in enumerate(temperatures):\n", + "# temp_group = pr.create_group('temp_' + str(i))\n", + "# for j in range(samples):\n", + "# job = temp_group.create.job.Lammps('npt_temp_' + str(i) + '_sample_' + str(j), delete_existing_job=True)\n", + "# job.structure = structure.copy()\n", + "# job.structure.bonds = get_bonds_list(stat_ba.output.per_shell_bond_relations[0].copy())\n", + "# job.potential = md_morse(b=6)\n", + "# job.calc_md(temperature=temp, pressure=None, n_ionic_steps=md_steps, n_print=md_samples,\n", + "# langevin=True, pressure_damping_timescale=100., time_step=1.)\n", + "# job.input.control.energy_pot_per_atom()\n", + "# job.write_restart_file()\n", + "# job.server.queue = 'cmti'\n", + "# job.server.cores = 4\n", + "# job.server.runtime = 3600\n", + "# job.run()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5047f781", + "metadata": { + "ExecuteTime": { + "end_time": "2022-09-26T13:24:54.986620Z", + "start_time": "2022-09-26T13:23:52.441768Z" + }, + "scrolled": true + }, + "outputs": [], + "source": [ + "## NVE\n", + "\n", + "for i, temp in enumerate(temperatures):\n", + " temp_group = pr.create_group('temp_' + str(i))\n", + " for j in range(samples):\n", + " job = pr.load('npt_temp_' + str(i) + '_sample_' + str(j))\n", + " job_nve = job.restart(job_type=pr.job_type.Lammps, job_name='nve_temp_' + str(i) + '_sample_' + str(j))\n", + " job_nve.calc_md(temperature=None, n_print=md_samples, n_ionic_steps=md_steps)\n", + " del job_nve.input.control[\"fix___langevin\"]\n", + " job.server.queue = 'cmti'\n", + " job.server.cores = 4\n", + " job.server.runtime = 3600\n", + " job_nve.run()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3c255818", + "metadata": { + "ExecuteTime": { + "end_time": "2022-09-26T13:27:10.391451Z", + "start_time": "2022-09-26T13:26:52.268307Z" + } + }, + "outputs": [], + "source": [ + "## to delete all LAMMPS files, and only keep the pyiron job\n", + "\n", + "for i in range(len(temperatures)):\n", + " for j in range(samples):\n", + " cleanup_job(job_npt)\n", + " cleanup_job(job_nve)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyiron_contrib/atomistics/mean_field/morse_al_mf_npt.ipynb b/pyiron_contrib/atomistics/mean_field/morse_al_mf_npt.ipynb new file mode 100644 index 000000000..42a7b2a66 --- /dev/null +++ b/pyiron_contrib/atomistics/mean_field/morse_al_mf_npt.ipynb @@ -0,0 +1,810 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "04ace7b5", + "metadata": {}, + "source": [ + "# Bond lattice mean-field model for a Morse potential - NPT" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "08515601", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:00:56.592869Z", + "start_time": "2022-12-01T14:00:45.158875Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/cmmc/u/system/SLES12/soft/pyiron/dev/anaconda3/lib/python3.8/site-packages/pkg_resources/__init__.py:123: PkgResourcesDeprecationWarning: NOT-A-GIT-REPOSITORY is an invalid version and will not be supported in a future release\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "02121f83981e414f9383b12ac4b67452", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from pyiron_atomistics import Project\n", + "\n", + "from scipy.interpolate import UnivariateSpline, CubicSpline\n", + "from scipy.optimize import root_scalar, root\n", + "from scipy.integrate import cumtrapz\n", + "\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "from matplotlib import gridspec\n", + "\n", + "from scipy.constants import physical_constants\n", + "KB = physical_constants['Boltzmann constant in eV/K'][0]\n", + "\n", + "from pyiron_contrib.atomistics.mean_field.core.bond_analysis import StaticBondAnalysis, MDBondAnalysis\n", + "\n", + "import seaborn as sns\n", + "sns.set_context('paper', font_scale=1.5, rc={\"lines.linewidth\": 2})" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c9c25b36", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:00:56.599018Z", + "start_time": "2022-12-01T14:00:56.593240Z" + } + }, + "outputs": [], + "source": [ + "alpha = 1.5\n", + "pr = Project('morse_al/mfm')\n", + "pr_md_npt = Project('morse_al/md_runs_npt/alpha_' + str(alpha).replace('.', '_'))" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d1e84e27", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:00:56.604967Z", + "start_time": "2022-12-01T14:00:56.599265Z" + } + }, + "outputs": [], + "source": [ + "# potential functions\n", + "D = 0.1\n", + "a_0 = 2.856\n", + "kappa = 0.\n", + "\n", + "# for lammps\n", + "def md_morse(D=D, alpha=alpha, r_0=a_0, b=1):\n", + " config = 'atom_style bond\\nbond_style morse\\n'\n", + " for i in range(b):\n", + " vals = (i+1, D, alpha, a_0)\n", + " config += 'bond_coeff %d %.7f %.7f %.7f\\n'%(vals)\n", + " return pd.DataFrame({'Name': ['Morse'],\n", + " 'Filename': [[]], \n", + " 'Model' : ['Morse'], \n", + " 'Species' : [['Al']], \n", + " 'Config' : [[config]]})\n", + "\n", + "# longitudinal_potential\n", + "def morse(r, D=D, alpha=alpha, a_0=a_0):\n", + " return D*(1.0+np.exp(-2.0*alpha*(r-a_0)) - 2.0*np.exp(-alpha*(r-a_0)))\n", + "\n", + "def dmorse(r, D=D, alpha=alpha, a_0=a_0):\n", + " return -2.0*alpha*D*(np.exp(-2.0*alpha*(r-a_0)) - np.exp(-alpha*(r-a_0)))\n", + "\n", + "# harmonic potential\n", + "def harm(r, kappa=kappa):\n", + " return D*alpha*alpha*kappa*(r**2)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "f1997bca", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:00:56.611207Z", + "start_time": "2022-12-01T14:00:56.605188Z" + } + }, + "outputs": [], + "source": [ + "# structure\n", + "element = 'Al'\n", + "potential = md_morse()\n", + "supercell = 4\n", + "n_atoms = 4*supercell**3\n", + "temperatures = np.linspace(100, 700, 7)\n", + "pressure = 0.0001 # in GPa\n", + "samples = 5" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ddf9c025", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:00:58.340952Z", + "start_time": "2022-12-01T14:00:56.611440Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The job zero was saved and received the ID: 18414826\n" + ] + } + ], + "source": [ + "# minimize structure\n", + "zero = pr.create.job.Lammps('zero', delete_existing_job=True)\n", + "zero.structure = pr.create.structure.bulk(element, cubic=True).repeat(supercell)\n", + "zero.potential = potential\n", + "zero.calc_minimize(pressure=pressure)\n", + "zero.run()\n", + "structure = zero.get_structure()\n", + "a_0 = (structure.cell/supercell/np.sqrt(2))[0][0]\n", + "U_0 = zero.output.energy_pot[-1]/n_atoms" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "8d6b917f", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:00:58.940776Z", + "start_time": "2022-12-01T14:00:58.344052Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The job stat_ba was saved and received the ID: 18414827\n" + ] + } + ], + "source": [ + "# analyze bonds and get rotations and displacement matrix\n", + "stat_ba = pr.create_job(StaticBondAnalysis, 'stat_ba', delete_existing_job=True)\n", + "stat_ba.input.structure = structure.copy()\n", + "stat_ba.input.n_shells = 1\n", + "stat_ba.run()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "6749c0f7", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:00:58.943521Z", + "start_time": "2022-12-01T14:00:58.941040Z" + } + }, + "outputs": [], + "source": [ + "# normalized displacement vectors along which to displace our atom\n", + "disp_matrix = np.array(stat_ba.output.per_shell_transformation_matrices[0][0])\n", + "# these are also the normalized long, t1 and t2 vectors\n", + "long_hat, t1_hat, t2_hat = disp_matrix\n", + "# rotations\n", + "rotations = stat_ba.output.per_shell_0K_rotations[0]\n", + "# a_xyz\n", + "a_xyz = long_hat*a_0" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "e3de985e", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:01:05.230492Z", + "start_time": "2022-12-01T14:00:58.943784Z" + } + }, + "outputs": [], + "source": [ + "# load the nvt jobs from the md runs\n", + "md_jobs_npt = []\n", + "for i in range(len(temperatures)):\n", + " temp_jobs = []\n", + " for j in range(samples):\n", + " temp_jobs.append(pr_md_npt.load('nve_temp_' + str(i) + '_sample_' + str(j)))\n", + " md_jobs_npt.append(temp_jobs)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "32ad89bd", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:01:06.654001Z", + "start_time": "2022-12-01T14:01:05.230854Z" + } + }, + "outputs": [], + "source": [ + "# extract mean anharmonic internal energy from the md run\n", + "def get_md_ah_U(temp_jobs, temperature, snapshots=200):\n", + " ah_U_pa = []\n", + " for job in temp_jobs:\n", + " if job.status == 'finished':\n", + " U_pa = np.mean(job['output/generic/energy_pot'][snapshots:])/n_atoms - U_0\n", + " T_virial = np.mean(job['output/generic/temperature'][snapshots:])\n", + " ah_U_pa.append(U_pa - 1.5*KB*T_virial)\n", + " return np.mean(ah_U_pa)*1000, np.std(ah_U_pa)/np.sqrt(len(temp_jobs))*1000 # in meV\n", + "\n", + "U_md_npt = []\n", + "U_md_npt_err = []\n", + "for (temp_jobs, temp) in zip(md_jobs_npt, temperatures):\n", + " ah_U_pa, ah_U_pa_err = get_md_ah_U(temp_jobs, temp)\n", + " U_md_npt.append(ah_U_pa)\n", + " U_md_npt_err.append(ah_U_pa_err)\n", + "U_md_npt = np.array(U_md_npt)\n", + "U_md_npt_err = np.array(U_md_npt_err)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "d2509a66", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:01:32.652213Z", + "start_time": "2022-12-01T14:01:06.654383Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The job md_ba_npt_100_0 was saved and received the ID: 18414828\n", + "The job md_ba_npt_200_0 was saved and received the ID: 18414829\n", + "The job md_ba_npt_300_0 was saved and received the ID: 18414830\n", + "The job md_ba_npt_400_0 was saved and received the ID: 18414831\n", + "The job md_ba_npt_500_0 was saved and received the ID: 18414832\n", + "The job md_ba_npt_600_0 was saved and received the ID: 18414833\n", + "The job md_ba_npt_700_0 was saved and received the ID: 18414834\n" + ] + } + ], + "source": [ + "# analyze MD bonds for the highest temperature\n", + "md_ba_jobs = []\n", + "for (temp, temp_jobs) in zip(temperatures, md_jobs_npt):\n", + " md_job = temp_jobs[0]\n", + " md_ba = pr.create_job(MDBondAnalysis, 'md_ba_npt_' + str(temp).replace('.','_'), delete_existing_job=True)\n", + " md_ba.input.structure = structure.copy()\n", + " md_ba.input.n_shells = 1\n", + " md_ba.input.md_job = md_job.copy()\n", + " md_ba.input.thermalize_snapshots = 200\n", + " md_ba.run()\n", + " md_ba_jobs.append(md_ba)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "7526d922", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:01:32.655085Z", + "start_time": "2022-12-01T14:01:32.652526Z" + } + }, + "outputs": [], + "source": [ + "# bond arrangement functions\n", + "\n", + "# for 1d\n", + "def get_b_array(long_samples, t1_samples, t2_samples):\n", + " b_array = np.outer(long_samples, long_hat) + np.outer(t1_samples, t1_hat) + np.outer(t2_samples, t2_hat)\n", + " return b_array\n", + "\n", + "# for 3d\n", + "def get_sample_mesh(long_samples, t1_samples, t2_samples):\n", + " long_mesh, t1_mesh, t2_mesh = np.meshgrid(long_samples, t1_samples, t2_samples, indexing='ij')\n", + " return long_mesh, t1_mesh, t2_mesh\n", + "\n", + "def get_b_grid(long_mesh, t1_mesh, t2_mesh):\n", + " b_grid = np.tensordot(long_mesh, long_hat, axes=0) + np.tensordot(t1_mesh, t1_hat, axes=0) + \\\n", + " np.tensordot(t2_mesh, t2_hat, axes=0)\n", + " return b_grid" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "bf701729", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:01:38.369951Z", + "start_time": "2022-12-01T14:01:32.655290Z" + } + }, + "outputs": [], + "source": [ + "# create bond vectors from the MD bond distribution (so a seperate one for each temperature)\n", + "n_bins = 51\n", + "long_meshs, t1_meshs, t2_meshs = [], [], []\n", + "for c in range(len(temperatures)):\n", + " md_rho, md_bins = md_ba_jobs[c].get_3d_histogram_long_t1_t2(n_bins=n_bins, shell=0)\n", + " long_meshs.append(md_bins[0])\n", + " t1_meshs.append(md_bins[1])\n", + " t2_meshs.append(md_bins[2])\n", + " \n", + "# since our rotations are done in the xyz coordinate system, we convert them to xyz\n", + "b_grids = []\n", + "for c in range(len(temperatures)):\n", + " b_grids.append(get_b_grid(long_meshs[c], t1_meshs[c], t2_meshs[c]))" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "07c0556b", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:01:38.371231Z", + "start_time": "2022-12-01T14:01:38.370259Z" + } + }, + "outputs": [], + "source": [ + "# # create custom meshs\n", + "# n_bins = 51\n", + "# long_samples = np.linspace(1.8, 4.5, n_bins)\n", + "# t1_samples = np.linspace(-1.5, 1.5, n_bins)\n", + "# t2_samples = t1_samples.copy()\n", + "# long_mesh, t1_mesh, t2_mesh = get_sample_mesh(long_samples, t1_samples, t2_samples)\n", + "# b_grids = [get_b_grid(long_mesh, t1_mesh, t2_mesh)]*len(temperatures)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "005004e3", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:01:38.400058Z", + "start_time": "2022-12-01T14:01:38.371583Z" + } + }, + "outputs": [], + "source": [ + "# single bonding potential\n", + "def V_1(b_xyz):\n", + " r = np.linalg.norm(b_xyz, axis=-1)\n", + " return morse(r)\n", + "\n", + "# single bonding potential gradient\n", + "def dV_1(b_xyz):\n", + " V = V_1(b_xyz=b_xyz)\n", + " long_samples = np.dot(b_xyz, long_hat)[:, 0, 0]\n", + " t1_samples = np.dot(b_xyz, t1_hat)[0, :, 0]\n", + " t2_samples = np.dot(b_xyz, t2_hat)[0, 0, :]\n", + " return np.gradient(V, long_samples, t1_samples, t2_samples, edge_order=2)\n", + "\n", + "# each mean field component\n", + "def V_mf_component(b_xyz, rotation, a_s=1., a_ex=1.):\n", + " \"\"\"\n", + " here, a_s=the 'global' strain and a_ex=the 'pseudo' strain, which is equal to the 'global' strain at P=0 for \n", + " a given temperature. Once a_ex is established for a temperature, it remains the same for that temperature for \n", + " all new 'global' strains that we would like our model to predict free energies for.\n", + " \"\"\"\n", + " return V_1((b_xyz-a_xyz*a_s*a_ex)@rotation.T+a_xyz*a_s*a_ex)\n", + "\n", + "# mean field potential\n", + "def V_mf(b_xyz, a_s=1., a_ex=1.):\n", + " vmf = V_1(b_xyz)/2\n", + " for rot in rotations[1:]:\n", + " vmf += V_mf_component(b_xyz=b_xyz, rotation=rot, a_s=a_s, a_ex=a_ex)/2\n", + " return vmf\n", + "\n", + "# mean field correlated potential\n", + "def V_mfc(b_xyz, a_s=1., a_ex=1.):\n", + " vmfc = V_mf_component(b_xyz=b_xyz, rotation=rotations[0], a_s=a_s, a_ex=a_ex)\n", + " for i, rot in enumerate(rotations):\n", + " if i not in [0, 1]:\n", + " vmfc += V_mf_component(b_xyz=b_xyz, rotation=rot, a_s=a_s, a_ex=a_ex)/2\n", + " return vmfc\n", + "\n", + "# # linear correction function 1-d\n", + "# def find_linear_correction(temperature=100., a_s=1., a_ex=1., nstep=10000, minr=0.1, maxr=5.):\n", + "# r = np.linspace(minr, maxr, nstep, endpoint=True)\n", + "# r_array = get_b_array(r, np.zeros(nstep), np.zeros(nstep))\n", + "# V = V_mfc(b_xyz=r_array, a_s=a_s, a_ex=a_ex)\n", + "# V -= V.min()\n", + "# sel = V/temperature/KB<200.0 \n", + "# r, V = r[sel], V[sel]\n", + "# aa = a_s*a_0\n", + "# def f(m):\n", + "# rho = brho * np.exp(-m*(r-aa)/temperature/KB)\n", + "# res = np.abs(np.log((rho*r).sum()/rho.sum()/aa)).sum()\n", + "# return res\n", + "# brho = np.exp(-(V-V.min())/temperature/KB)\n", + "# solver = root_scalar(f, x0=0.0, x1=0.01, rtol=1e-8)\n", + "# return solver.root\n", + "\n", + "# linear correction function\n", + "def find_linear_correction(b_xyz, temperature=100., a_s=1., a_ex=1.):\n", + " \"\"\"\n", + " since the 1-d linear correction was not correctly setting =a (bug in the code?) I use the 3d bond vectors\n", + " to find the linear correction. This works.\n", + " \"\"\"\n", + " V = V_mfc(b_xyz=b_xyz, a_s=a_s, a_ex=a_ex)\n", + " V -= V.min()\n", + " long_mesh = np.dot(b_xyz, long_hat)\n", + " sel = V/temperature/KB<200.0 \n", + " long_mesh, V = long_mesh[sel], V[sel]\n", + " aa = a_s*a_0\n", + " def f(m):\n", + " rho = brho*np.exp(-m*(long_mesh-aa)/temperature/KB)\n", + " res = np.abs(np.log((rho*long_mesh).sum()/rho.sum()/aa)).sum()\n", + " return res\n", + " brho = np.exp(-(V-V.min())/temperature/KB)\n", + " solver = root_scalar(f, x0=0.0, x1=0.01, rtol=1e-8)\n", + " return solver.root\n", + "\n", + "# mean field correlated potential with linear correction term\n", + "def V_mfc_lc(b_xyz, temperature=100., a_s=1., a_ex=1.):\n", + " lm = find_linear_correction(b_xyz=b_xyz, temperature=temperature, a_s=a_s, a_ex=a_ex)\n", + " # NOTE here that the additional local strain is NOT considered here, as this will make the model predict\n", + " # the correction term at that strain, thus violating =a.\n", + " lc = lm*(np.dot(b_xyz, long_hat)-a_0*a_s)\n", + " vmfclc = V_mfc(b_xyz=b_xyz, a_s=a_s, a_ex=a_ex)+lc\n", + " return vmfclc\n", + "\n", + "# helper method to find virial quantities\n", + "def virial_helper(b_xyz, a_s=1., a_ex=1., pressure=False):\n", + " dV = np.array(dV_1(b_xyz))\n", + " if not pressure:\n", + " db_dV = (np.dot(b_xyz, long_hat)-a_0*a_s)*dV[0] + np.dot(b_xyz, t1_hat)*dV[1] + np.dot(b_xyz, t2_hat)*dV[2]\n", + " else:\n", + " db_dV = a_0*a_s*dV[0] # + np.dot(b_xyz, t1_hat)*dV[1] + np.dot(b_xyz, t2_hat)*dV[2]\n", + " return db_dV\n", + "\n", + "# bonding density function\n", + "def get_rho(v, temperature=100.):\n", + " rho = np.exp(-(v-v.min())/KB/ temperature)\n", + " return rho/rho.sum()\n", + "\n", + "# virial temperature\n", + "def find_virial_T(b_xyz, temperature=100., a_s=1., a_ex=1.):\n", + " db_dV = virial_helper(b_xyz, a_s=a_s, a_ex=a_ex)\n", + " vmfclc = V_mfc_lc(b_xyz=b_xyz, temperature=temperature, a_s=a_s, a_ex=a_ex)\n", + " rho = get_rho(v=vmfclc, temperature=temperature)\n", + " return 2./KB*(rho*db_dV).sum()\n", + "\n", + "# virial pressure\n", + "def find_virial_P(b_xyz, temperature=100., a_s=1., a_ex=1.):\n", + " db_dV = virial_helper(b_xyz, a_s=a_s, a_ex=a_ex, pressure=True)\n", + " vmfclc = V_mfc_lc(b_xyz=b_xyz, temperature=temperature, a_s=a_s, a_ex=a_ex)\n", + " rho = get_rho(v=vmfclc, temperature=temperature)\n", + " N_by_V = n_atoms/((a_0*a_s*np.sqrt(2)*supercell)**3)\n", + " return N_by_V*(KB*temperature+4.*((rho*db_dV).sum()))\n", + "\n", + "# 2 virials, 1 method\n", + "def find_virial(b_xyz, temperature=100., a_s=1., a_ex=1.):\n", + " \"\"\"\n", + " Find both the virial temperature and pressure in the same method.\n", + " \"\"\"\n", + " vmfclc = V_mfc_lc(b_xyz=b_xyz, temperature=temperature, a_s=a_s, a_ex=a_ex)\n", + " rho = get_rho(v=vmfclc, temperature=temperature)\n", + " db_dV_T = virial_helper(b_xyz=b_xyz, a_s=a_s, a_ex=a_ex)\n", + " virial_T = 2./KB*(rho*db_dV_T).sum()\n", + " db_dV_P = virial_helper(b_xyz, a_s=a_s, a_ex=a_ex, pressure=True)\n", + " N_by_V = n_atoms/((a_0*a_s*np.sqrt(2)*supercell)**3)\n", + " virial_P = N_by_V*(KB*temperature+4.*((rho*db_dV_P).sum()))\n", + " return virial_T, virial_P\n", + "\n", + "# effective temperature\n", + "def find_eff_T(b_xyz, temperature=100., a_s=1., a_ex=1.):\n", + " print(\"Initial temperature: {}\".format(temperature))\n", + " def dvirialE(eff_temperature):\n", + " res = find_virial_T(b_xyz=b_xyz, temperature=eff_temperature, a_s=a_s, a_ex=a_ex)\n", + " return res/(temperature+1e-20)-1.\n", + " solver = root_scalar(dvirialE, x0=temperature, x1=temperature+10, rtol=1e-8)\n", + " print(\"Effective temperature: {}\".format(solver.root))\n", + " return solver.root\n", + " \n", + "# effective temperature + strain\n", + "def find_eff_strain_and_T(b_xyz, temperature=100., pressure=1e-10, a_s=1., a_ex=1.):\n", + " \"\"\"\n", + " Optimize both the effective temperature and strain corresponding to a given pressure. Presently, I use this\n", + " method to set my pressure to 0 to obtain a_ex.\n", + " \"\"\"\n", + " def dvirialPT(arg):\n", + " res_T, res_P = find_virial(b_xyz=b_xyz, temperature=arg[0], a_s=arg[1], a_ex=arg[1])\n", + " return [res_T/(temperature+1e-20)-1., res_P/(pressure+1e-20)-1.]\n", + " solver = root(dvirialPT, x0=(temperature, a_s), tol=1e-8)\n", + " print('Effective temperature: {}\\nGlobal strain: {}'.format(solver.x[0], solver.x[1]))\n", + " return solver.x\n", + "\n", + "# strain_at_pressure\n", + "def find_strain_at_pressure(b_xyz, temperature=100., pressure=1e-20, a_ex=1., a_s=1.):\n", + " \"\"\"\n", + " seperate method to find the strain at an input pressure.\n", + " \"\"\"\n", + " def f(a_s):\n", + " res = find_virial_P(b_xyz=b_xyz, temperature=temperature, a_s=a_s, a_ex=a_ex)\n", + " return res/(pressure+1e-20)-1.\n", + " solver = root_scalar(f, x0=a_ex*0.99, x1=a_ex*1.01, rtol=1e-8)\n", + " return solver.root\n", + "\n", + "# get ah U's using this function...\n", + "def get_ah_U(rhos, temperatures=temperatures):\n", + " return np.array([(6*(((V_1(b_grids[i]) - V_1(a_xyz))*rho).sum()) - 1.5*KB*t)*1000 \n", + " for i, (rho, t) in enumerate(zip(rhos, temperatures))])\n", + "\n", + "# get ah F's using this function...\n", + "def get_ah_F(ah_Us, temperatures=temperatures, n_fine=10000):\n", + " fine_temperatures = np.linspace(temperatures[0], temperatures[-1], n_fine, endpoint=True)\n", + " ah_U_eqn = UnivariateSpline(x=temperatures, y=ah_Us, k=3, s=0)\n", + " return -cumtrapz(ah_U_eqn(fine_temperatures), 1/fine_temperatures)*fine_temperatures[1:], fine_temperatures[1:]" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "73c19a10", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:02:00.779305Z", + "start_time": "2022-12-01T14:01:38.400351Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Effective temperature: 99.79150421106391\n", + "Global strain: 1.0062102234640375\n", + "Effective temperature: 198.94728796409652\n", + "Global strain: 1.0128133122669747\n", + "Effective temperature: 297.1438676690236\n", + "Global strain: 1.019980475171334\n", + "Effective temperature: 393.255183143703\n", + "Global strain: 1.0279369695867742\n", + "Effective temperature: 486.51875395186016\n", + "Global strain: 1.0368166845435047\n", + "Effective temperature: 574.8254791620155\n", + "Global strain: 1.0472475401673842\n", + "Effective temperature: 654.1924151591791\n", + "Global strain: 1.060014706701452\n" + ] + } + ], + "source": [ + "# collect effective temperatures and pseudo strains for each temperature using the new approach\n", + "eff_parameters = np.array([find_eff_strain_and_T(b_xyz, t, pressure) for b_xyz, t in zip(b_grids, temperatures)]).T\n", + "eff_temperatures_new, pseudo_strains = eff_parameters" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "907a0496", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:02:10.696215Z", + "start_time": "2022-12-01T14:02:00.779673Z" + } + }, + "outputs": [], + "source": [ + "# collect global strains for the given pressure using the pseudo strains and effective temperatures\n", + "global_strains = np.array([find_strain_at_pressure(b_grid, temp, pressure, a_ex) \n", + " for temp, b_grid, a_ex in zip(eff_temperatures_new, b_grids, pseudo_strains)])" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "682ce597-dbc6-427d-8a5f-8f45bc31532e", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:02:12.233623Z", + "start_time": "2022-12-01T14:02:10.696580Z" + } + }, + "outputs": [], + "source": [ + "# ah_Us and ah_Fs\n", + "\n", + "# fixed pressure = global strain and pseudo strain\n", + "vmfcv_ps = []\n", + "mfcv_ps_rhos = []\n", + "for i, (eff_temp, s, ex) in enumerate(zip(eff_temperatures_new, global_strains, pseudo_strains)):\n", + " v = V_mfc_lc(b_grids[i], a_s=s, a_ex=ex, temperature=eff_temp)\n", + " vmfcv_ps.append(v)\n", + " mfcv_ps_rhos.append(get_rho(v, eff_temp))\n", + "U_mfcv_ps = get_ah_U(mfcv_ps_rhos, temperatures=temperatures)\n", + "F_mfcv_ps, fine_temperatures = get_ah_F(U_mfcv_ps, temperatures=temperatures)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "4736e364-ec0a-4750-bef4-fab3fcd555f3", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:02:12.568072Z", + "start_time": "2022-12-01T14:02:12.233929Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkIAAAHTCAYAAADLdXd7AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8o6BhiAAAACXBIWXMAAA9hAAAPYQGoP6dpAACfnElEQVR4nOzdd3ib1fn/8bcky5aHbHnv2Nl7T7IIEKCQHVJKS6CkUEJTIBS+8AsUCKGssksJJS1QEkah7FkgUBJo9p4OWd52vC1bHrIlPb8/ZMtWLDmWLceyfb+uK1fRM49kNf7kPOecW6UoioIQQgghRC+k7uoGCCGEEEJ0FQlCQgghhOi1JAgJIYQQoteSICSEEEKIXkuCkBBCCCF6LQlCQgghhOi1JAgJIYQQoteSICSEEEKIXkuCkBBCCCF6LQlCQvigTZs2oVKpeOihh7q6Kd3KQw89hEqlYtOmTV6/9g033IBKpSIjI8Pr1+4JVCoVs2bN6upmCOExCUKiS6lUKlQqFSkpKdTW1ro8JjU1FZVKhcVicXlu4x+NRkNUVBSXXHIJ77zzjtvjzvXn9ddf78y3LHq5119/Xb5nQvgQv65ugBAAWVlZPP/886xatcrjc1evXg1AfX09P/30Ex9//DH//e9/2bNnD0899ZRjf3PPP/88RqORlStXYjAYnPaNGTOmPW9B+IBbb72Va665hj59+nj92o8//jirVq0iMTHR69cWQnQdCUKiy4WHh6NSqXj88ce56aabiIqK8uj8sx8ffffdd1x66aU8++yz3HrrrS4fL73++usYjUbuuOMOUlNT29944VOioqI8/v60VXx8PPHx8Z1ybSFE15FHY6LLBQUF8cADD1BRUcGaNWs6fL1LLrmEIUOGYLPZ2LVrlxda6MxoNPLUU09x8cUXk5SUhL+/P9HR0cyfP5+tW7e6PKdx/ERxcTE333wz8fHxBAQEMHz4cF599dVW77d//37mzJmDwWAgKCiImTNnsmXLlhbH5eXl8fDDDzNt2jTi4uLw9/cnISGBX/7ylxw5cqTF8RkZGahUKm644QaOHTvGkiVLiI6ORq1Ws2nTJqf9p06dYsmSJURGRqLX67nssss4fPgwAAUFBdx4443Ex8ej0+mYOHGi2zE65eXlrFq1ikGDBqHT6QgPD+eyyy5j48aNLY5tPk6qrZ9Ba2OEjh07xm9+8xtSU1MJCAggJiaGGTNm8Le//a3Vz7+RqzFCzT+jjIwMrrnmGqKiotDpdIwfP55PP/3U6RqzZs1i2bJlACxbtszpkWzz61osFl566SWmTJlCaGgoQUFBjB07lhdffBGbzeZ0TU9+jm1pI7TvO94eO3bsYMmSJY7va3JyMsuXLycvL6/FsbNmzXI8In/ssccYOHAgAQEBJCcnc/fdd2M2m13e49ixY9xwww0kJycTEBBAbGwsv/rVr/jpp59aHNv4Mz59+jTPP/88I0eOJDAw0Gns0/Hjx7nqqqsIDw8nODiYqVOn8sUXX7R45Gm1WklOTiY0NBSTyeSybbfeeisqlYoPPvjA8w9PeI8iRBcClMTERKWurk7p37+/otVqlZ9++snpmJSUFAVQ6uvrW5zr7is8ZMgQBVDee+89l/sbr5menu5xm7dt26ZotVrlkksuUW6++Wbl//2//6dcc801SmBgoKLRaJQvvvjC5fscPXq0MmjQIGXEiBHKrbfeqtx0002KwWBQAOW1115zOv77779XAGXOnDlKYGCgcvHFFyt33XWX8vOf/1xRq9VKQECAcvToUadz/vWvfymBgYHKlVdeqaxYsUK5++67lYULFyp+fn5KUFCQsm/fPqfj09PTFUCZNm2aEhYWpkyaNEm54447lJtuuknZvXu3Y/+FF16oREZGKtOnT1fuvPNOZfHixYpKpVIiIyOVY8eOKampqcqYMWOUlStXKtdff72i1WqVgIAAJTMz0+l+paWljp/LpEmTlP/3//6fcuONNyp6vV5RqVTK2rVrO/wZrF69WgGU77//3mn7559/rgQGBipqtVq58sorlVWrVinLly9XpkyZoqSmprblx678+te/bvGdafyMZs2apURHRyuTJ09W7rjjDuX6669XAgICFJVKpXz77beO4//5z38qCxYsUABlwYIFyurVqx1/ysrKFEVRlLq6OuXyyy9XAGXIkCHK8uXLlZUrVyqjRo1SAOXaa69t18+xrW1UlPZ/xy+88MI2fZaKoiivvfaaotFolODgYOWXv/yl4/uqVquV+Pj4Ft+fCy+8UAGUn//850pcXJyybNkyZeXKlcrAgQMVQLn++utb3OM///mPEhgYqGi1WmXRokXK3Xffrfzyl79UAgIClNDQUGXPnj1Oxzf+jOfMmaOEhYUpv/rVr5R77rlHuffeexVF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OnSI2bNnEx4ejkql4qGHHvLKdV9//XVUKhWbNm3yyvWEEEK4Z6yu52h+hdv9WaXVZJVWAxCq82NEYtj5ato5+fT0edGzWSwWFi9ejNls5k9/+hMGg4FRo0Z1dbOEEEJ4aNvpErc1wxRFYeupYsfrC/pHolGrzlfTzkmCkOgyp0+f5uTJkzz77LPceuutXd0cIYQQ7VBiMnPsjPveoFNFVRRUmAGICvFncKz+fDWtTeTRmOgyZ87Yi+yFh3f99EkhhBDts+10CYqboUE2m3Nv0NT+UahUvtMbBBKERDs1jsH57rvveOyxx+jXrx86nY7Ro0fzn//8B4CjR48yd+5cwsLCCAsL4/rrr6eyshKA1NRULrzwQgCWLVuGSqVCpVKRkZHhdI9p06YRGhpKUFAQQ4YM4fbbb6eurq7d7a6vr+e5555j/PjxBAcHo9frGTVqFKtXr271vIqKCoKCgrj00ktd7n/llVdQqVS89dZbrV4nIyPDMRbq3XffZezYseh0OhISErjzzjupqqpyOr6srIy7776bgQMHEhgYSFhYGMOGDePOO+/07I0LIUQnKKyo5WTDTDBX0s5UUFZdD0B8mI7UyKDz1bQ2k0djokNWrVpFXV0dv/vd79BoNLzwwgssWLCA999/nxtvvJGrr76aefPmsW3bNtavX09AQAD/+Mc/eP7559m1axePPfYYN998MzNmzAAgOjoagF//+tds2LCBcePGcc899xAdHc2pU6f48MMPefjhh/H39/e4rfX19VxxxRV89913zJo1i9WrVxMSEsKxY8d47733WLNmjdtzQ0NDWbRoEe+88w45OTkkJSU57V+/fj2hoaEsXry4TW357LPPePbZZ1mxYgU33XQT3377Lc899xwHDhxg48aNqNX2f6NcffXVbNq0id/+9reMHTsWs9nMyZMn+e9//+vx+xdCCG/besp9b5DFamP76VLH62kDfK83CCQIiQ6qq6tj586dBAQEAPb6cKNHj2bhwoW88847XH311QAsX76c8vJy1q9fz3PPPcfChQsxGAw89thjXHDBBSxdutRxzffff58NGzawZMkS/vWvf+Hn1/Q1/fOf/9zutv7lL3/hu+++4w9/+APPPvus0z6bzf0CYI1uuOEG3n77bTZs2MB9993n2H7q1Cn+97//8dvf/pbAwMA2tWXfvn1s377dUTj497//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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.title('NPT anharmonic internal energy')\n", + "plt.xlabel('Temperatures [K]')\n", + "plt.ylabel('$U_{\\mathrm{ah}}$ [meV]')\n", + "plt.plot(temperatures, U_md_npt, label='MD')\n", + "plt.fill_between(temperatures, U_md_npt-U_md_npt_err, U_md_npt+U_md_npt_err, alpha=0.5)\n", + "plt.plot(temperatures, U_mfcv_ps, label='mfc-lc-v-ps')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "ebcb8647-fabb-4ee4-953b-ef4ae24ab553", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:02:12.753524Z", + "start_time": "2022-12-01T14:02:12.568250Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkIAAAHTCAYAAADLdXd7AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8o6BhiAAAACXBIWXMAAA9hAAAPYQGoP6dpAACGeUlEQVR4nO3dd3QUVRvH8e+m904gkJDQewu9B0RRehOQ3rGgKIqir3RsiNhQQelVqggWEJWi9NB7TyAhlJAC6WXv+8eSDWsCJGGTTTbP55wcmDuzM88OSfbHzJ17NUophRBCCCFEMWRh6gKEEEIIIUxFgpAQQgghii0JQkIIIYQotiQICSGEEKLYkiAkhBBCiGJLgpAQQgghii0JQkIIIYQotiQICSGEEKLYkiAkhBBCiGJLgpAQZm7Hjh1oNBqmTJli6lKKlClTpqDRaNixY0e+7P/ixYv07NkTHx8fLCwscHNzy5fjCCEeTYKQMBsajQaNRoO/vz9JSUnZbhMQEIBGoyEtLS3b12Z8WVpa4uXlxVNPPcWPP/740O0e97V48eL8fMuiiNJqtXTv3p1ffvmFjh07MmnSJCZMmGDqsoQolqxMXYAQxnb16lW++OKLPH2wTJ48GYDU1FTOnTvHxo0b+fvvvzl06BCffvqpfv2DvvjiC2JjYxk7dmyW/9XXrVs3L29BFAJjxoyhb9++lC1b1uj7vnz5MidPnmTkyJF8//33Rt+/ECLnJAgJs+Lu7o5Go+Gjjz5ixIgReHl55er1/7199Ndff/H0008ze/ZsxowZk+3tpcWLFxMbG8vrr79OQEBA3osXhYqXl1euv39y6vr16wCULl06X/YvhMg5uTUmzIqDgwMTJ07k7t27TJ069Yn399RTT1G1alW0Wi0HDx40QoWGYmNj+fTTT2nbti2+vr7Y2NhQokQJunTpwp49e7J9jUajISgoiMjISEaNGoWPjw+2trbUqFGDBQsWPPJ4R48epWPHjri5ueHg4ECrVq3YvXt3lu2uX7/OtGnTaN68OaVKlcLGxobSpUvzwgsvcOrUqSzbh4SEoNFoGDJkCGfPnqVXr16UKFECCwsLduzYYbD+0qVL9OrVC09PT5ydnXnmmWc4efIkADdv3mT48OH4+PhgZ2dHw4YNH9pHJyYmhgkTJlC5cmXs7Oxwd3fnmWeeYdu2bVm2fbCfVE7PwaP6CJ09e5Zhw4YREBCAra0t3t7etGzZku++++6R5x90/36tW7cGYOrUqfrbqBkh+8HjLl26lIYNG+Lo6GgQshMSEvjoo4+oW7cujo6OODk50bRpU1atWvXQ427dupUOHTrg5eWFra0tFSpUYPz48cTExDy25gelpaXx7bff0qRJE1xcXHBwcKBevXrMmTMHrVZrsO2D/+4hISH07dsXLy8v7OzsqF+/Pps2bXrocVatWkWbNm1wd3fHzs6OatWqMWPGDJKTk7Nsm/Ezcf36dYYOHYqPjw+WlpYGt6ZXrFhBYGAg9vb2eHt7M3DgQK5fv05QUBAajUa/3ZYtW9BoNAwbNizbupKTk/UhObtaRBGkhDATgCpTpoxKSUlRFSpUUNbW1urcuXMG2/j7+ytApaamZnntw34cqlatqgC1du3abNdn7PPKlSu5rnnv3r3K2tpaPfXUU2rUqFHqnXfeUX379lX29vbK0tJS/frrr9m+zzp16qjKlSurmjVrqjFjxqgRI0YoNzc3BaiFCxcabL99+3YFqI4dOyp7e3vVtm1b9eabb6rnn39eWVhYKFtbW3X69GmD16xatUrZ29urDh06qJdfflmNHz9edevWTVlZWSkHBwd15MgRg+2vXLmiANW8eXPl6uqqGjVqpF5//XU1YsQIFRwcrF/funVr5enpqVq0aKHGjRunevTooTQajfL09FRnz55VAQEBqm7dumrs2LFq0KBBytraWtna2qrQ0FCD40VFRen/XRo1aqTeeecdNXz4cOXs7Kw0Go365ptvnvgcTJ48WQFq+/btBu2//PKLsre3VxYWFqpDhw5qwoQJavTo0apJkyYqICDgsf/mkydPVoMHD9afj8mTJ6vJkyfrj5Nx3I4dOypbW1vVq1cv9fbbb6tRo0YppZSKjo5W9erVU4CqX7++GjNmjHr55ZdVhQoVFKD+97//ZTnm1KlTFaA8PT3VoEGD1FtvvaWeeeYZBajq1aurmJiYx9atlFIpKSmqffv2ClBVq1ZVo0ePVmPHjlW1a9dWgOrfv7/B9hn/7kFBQapEiRKqcePG6vXXX1eDBg1Stra2SqPRqD///DPLcYYNG6YA5efnp4YPH67GjRunmjVrpt9XSkqKwfaAqlmzpipbtqyqUaOGGjNmjHrllVf0Pz8zZ85UgHJ3d1ejR49Wb7/9tqpXr54KCAhQderUMfjZ12q1qkKFCsrBwSHb87J8+XIFqDfffDNH50wUfhKEhNnICEJKKbV27VoFqO7duxtsk9sg9PfffysLCwul0WgeGnSeJAjFxMSo27dvZ2kPCQlRJUuWVFWqVMmyLqPW4cOHq7S0NH37qVOnlKWlpapatarB9hkhAFCLFy82WDd37lwFqBdffNGg/ebNm+ru3btZjn3o0CHl4OCg2rdvb9Ce8YEHqHfffTfL6x5cP2PGDIN106ZNU4BydXVVo0ePVunp6fp1K1asUIB6/fXXDV4zcuRIBaiXXnrJoP3s2bPK2dlZWVtbq8uXLz/ROcguCN2+fVu5uLgoa2trtXPnzizv8+rVq1naspNRz+TJk7Osyziug4ODOnz4cJb1GSFq1qxZBu2JiYmqffv2SqPRGLzu77//1ofU/36wL1q0SAFq7NixOao7o7axY8cafO+lpaXpw8tPP/2kb3/w333KlCkG+9qyZYsC1LPPPpttTb169VKJiYnZHv/zzz83aM84xsCBA7P8bF+6dElZWVkpLy8vg38frVar+vbtm+3P/qeffqoA9fXXX2c5By1btlQajSbLf7JE0SVBSJiNB4OQUko1bdpUAeqff/7Rtz0uCGX87/y9995TvXr1UlZWVgpQb7zxxkOP+yRB6FHGjBmjgCxXQzI+JLMLKq1atVKAwbqMD90WLVpk2T4lJUVZWVmp+vXr57iuTp06KVtbW4P/lWd84JUsWVIlJSVleU3G+oCAAIMPUKWUCg0Nfeh7SktLU9bW1iooKEjflpycrOzt7ZWTk5OKiorKcqz33ntPAWrq1KlPdA6yC0KzZs1SgHrttdcecnZyJidBKLtwEhkZqSwtLVXDhg2z3e/Ro0cVoN566y19W7du3RSgTp06le1r6tatq0qUKPHYmtPT05Wnp6fy8fHJ8m+olO5KlUajUb169dK3PerfXSmlypYtqzw9PbPUY21traKjo7Nsn5aWpjw9PVWDBg0M2gFlY2Ojbt68meU106dPz/L9kCEkJERZWlpmCUJ37txRdnZ2qlatWgbtp0+fVoBq27Ztln2Joks6Swuz9dlnn9GsWTPefPNN9u3bZ9AP4GEy+hVpNBrc3Nxo0aIFw4cPZ8CAAflW5+7du/nyyy/Zu3cvt27dIiUlxWB9eHh4lieXKleujLOzc5Z9+fn5Abr+M/9d36BBgyzbW1tbU7JkSaKjo7Os+/XXX5k7dy7BwcFERkZmGXIgMjISHx8fg7Y6depga2v70Pdat25dLC0tDdoyOgxn954sLS3x9vYmLCxM33bu3DkSExNp0aIF7u7uWY7Rrl07PvzwQw4fPpxlXW7PwX/t27cPgOeee+6x2z6pxo0bZ2k7ePAg6enpQNaO/aB72hF0fZgy7N27F2tra9asWZPtcVJSUrh9+zZ37tzB09PzofWcP3+eO3fuUKlSJaZPn57tNvb29gbHzpDdvzvovl/37t2rX05ISODYsWN4eXnxxRdfZHsMW1vbbI8REBCAt7d3lvYjR44A0KJFiyzr/P398fPzIyQkxKDdw8ODPn36sGTJEvbu3UvTpk0BmDdvHgCjR4/OtjZRNEkQEmaradOm9OrVi3Xr1rFmzRr69Onz2NcopQqgskw//fQTvXr1ws7OjqeffpoKFSrg6Oio72S8c+fObDtkurq6Zrs/Kyvdj3TGh2VOX/Pf7b/66ivGjh2Lu7s7Tz/9NGXLlsXBwQGNRsPGjRs5duxYtnWVKlXqke83uxoyan5UfRkf8KDrYP6oY2WEs4ztHnf8jGNkd87+K6NjcZkyZR677ZPK7v3duXMH0AWiR3Xej4uLM3hNWlraYx8eiIuLe2QQyjj2hQsXHrmvB4+d4VHn/cEO1tHR0SiluH37dq4fdnjY90PG90HJkiWzXV+yZMksQQjgpZdeYsmSJXz//fc0bdqUpKQkli5dire3N927d89VbaJwkyAkzNrHH3/Mzz//zLvvvlsof3lNnDgRGxsbgoODqVatmsG60aNHs3PnzgKvKS0tjcmTJ1OqVCkOHz6c5arPg/+D/6+cXHV7Uhkfqjdu3Mh2fUREhMF2xpQxTlR4eDi1atUy+v4flN25zHhPb7zxBrNnz87RflxdXdFqtURFRT1RPRnH7t69Oxs2bHiifT3uGPXq1cv2it6jPOx7z8XFBdA9kVijRo0s62/evJnt6xo3bkz9+vVZvXo1n3/+OZs2bSI6OpoJEyZgbW2dq9pE4SaPzwuzVqFCBV5++WWuXLnC119/bepysrh48SLVq1fPEoK0Wi3//vuvSWqKjIwkJiaGZs2aZQlBcXFxuf6AMrYqVarg4ODA0aNHs72dtX37dgACAwONfuwmTZoAukfRTaFRo0ZYWFjwzz//5Pg1TZo0ITo6OtthD3KjatWquLm5sW/fPoMrdMbk5OREjRo1OHXq1BMHtwz16tUDyPbnKTQ0lGvXrj30tS+99BKJiYksW7aMefPmodFoGDlypFHqEoWHBCFh9iZNmoSbmxsffPBBtpftTSkgIIALFy4QHh6ub1NKMXXqVE6fPm2Smry9vXFwcCA4ONjgfKWmpjJ27FgiIyNNUlcGGxsb+vfvT1xcHJMmTTJYd+nSJb766iusra0ZOHCg0Y89ePBgXFxc+Pbbb7Mde+jBvkz5wdvbm/79+xMcHMz06dOz9NsC3Tm4cuWKfvmNN94AYOTIkfqBHB8UHx+v7/v0KFZWVrz66qtERETw2muvkZiYmGWbiIiIJ/6+HTduHCkpKQwbNizbMY6io6NzFcb79euHlZUVX3/9tUHoUUrx7rvvPvKW6AsvvICbmxsfffQRe/bs4ZlnnqF8+fK5ej+i8JNbY8LseXh48N577/H222+bupQs3njjDV588UUCAwPp2bMn1tbW7N69m9OnT9O5c2c2b95c4DVZWFjw2muv8fHHH1OrVi26du1KSkoK27dvJyoqijZt2uivupjKxx9/zD///MOcOXM4ePAgbdq0ITIykjVr1nDv3j3mzJlDuXLljH5cLy8vVq5cSa9evWjVqhUdOnSgVq1axMbGcvz4ccLCwgxCSH6YM2cOFy5cYNKkSSxbtowWLVpQsmRJrl+/zpkzZzh48CCrVq3Sv/+nnnqKjz/+mHfffZdKlSrRoUMHypUrR1xcHKGhoezcuZMWLVqwZcuWxx574sSJHDt2jLlz57J582batm1LmTJluHXrFhcuXGD37t188MEHVK9ePc/vb9iwYRw6dIhvv/2WChUq0L59e8qWLUtUVBRXrlxh165dDB06lLlz5+ZofxUqVGDatGm899571KlThz59+uDq6sq2bduIioqiTp06HD9+PNvXOjg4MHjwYL788ktAOkmbK7kiJIqF1157rVBOfzF69GgWLVqEj48PS5YsYcWKFfj5+bF///58ubWTU9OnT+ezzz7D3t6eefPmsWHDBho0aMCBAwfyZe6t3PLw8GDv3r28/fbb3Llzh9mzZ7N27VoaNWrEli1bePnll/Pt2B07diQ4OJj+/ftz+PBhZs2axbp167CwsODdd9/Nt+NmcHFxYefOnXz99dd4eXmxfv16Zs+ezfbt23F2dubzzz/n6aefNnjNO++8w65du+jYsSO7d+/miy++YO3atYSHhzNq1ChmzJiRo2NbW1uzceNGli5dSpUqVfjll1/47LPP2LJlC1qtlunTp9O/f/8nfo/ffPMNmzdvpmnTpvz555/Mnj2bTZs2ERsby/jx43n99ddztb93332XpUuX4u/vz6JFi1iwYAHVqlVj9+7dpKWlPbI/WcYI06VLl6Zz585P8rZEIaVRBf2YjBBCCFEI3L17l5IlS1K3bt2HPgSwcOFChg8fzsSJE5k2bVoBVygKglwREkIIYdZu376dpYN3Wloab775JklJSfTs2TPb16WlpfH5559jbW0tt8XMmPQREkIIYdbWr1/PpEmTaNeuHX5+fkRFRbFr1y7Onz9PYGAgY8aMMdh+165dbN++nR07dnDy5EnGjh1bIGNHCdOQICSEEMKsNW7cmNatW7Nnzx5u3bqFUopy5crx/vvv884772BnZ2ew/d9//83UqVPx9PTkxRdf5OOPPzZR5aIgSB8hIYQQQhRb0kdICCGEEMWWBCEhhBBCFFvSR+gxIiMj2bp1KwEBAdjb25u6HCGEEELkQGJiIiEhIbRv3x4vL6+HbidB6DG2bt3KgAEDTF2GEEIIIfJg+fLljxzoU4LQY2SMRrx8+fIsE2MKIYQQonA6c+YMAwYMeOysAhKEHiPjdli1atVMOuWBEEIIIXLvcd1apLO0EEIIIYotCUJCCCGEKLYkCAkhhBCi2JIgJIQQQohiS4KQEEIIIYoteWrMyJRS+i8hTEGj0ei/hBBCPJoEISOKiorizp07pKWlmboUUcxZWVnh6emJh4eHqUsRQohCTYKQkSQkJHD79m1Kly6Ng4ODqcsRxVxCQgLXr1/Hzs5Ovh+FEOIRJAgZya1bt/D09MTZ2dnUpQiBs7Mznp6e3Lp167GjqgohRHEmnaWNQClFcnKyhCBRqDg7O5OcnCz91YQQ4hEkCBmBUgqtVouVlVxgE4WHlZUVWq1WgpAQQjyCBCEjkA8aUZjJ96cQQjycBCEhhBBCmMT3uy6x+2KkSWuQezlCCCGEKHBnIu7y8e9n0Sp4vr4vnz5fxyR1yBUhIYQQQhQopRSTfj6J9v6d+wAvR5PVIkFI5MmOHTv0oxcPHjw4222UUgQEBKDRaAw6kk+ZMsVg9GNra2s8PT2pV68eo0aNYseOHQX0LoQQQpjCT0fCORgSDUA5L0dGtCxnslrk1ph4InZ2dqxbt46vv/4aFxcXg3Xbtm0jNDQUOzs7UlNTs7x24sSJVK5cGa1WS2xsLKdPn2bjxo388MMPdO7cmZUrV+Lk5FRQb0UIIUQBuJuUyoe/ndUvT+5cHVsrS5PVI1eExBPp0aMHCQkJrFq1Ksu6+fPnU7ZsWRo2bJjta5955hkGDBjAoEGDePXVV/nuu+8IDQ3lxRdfZPPmzQwcODC/yxdCCFHAvth2gci4ZADa1yhJUBVvk9YjQUg8kWrVqtGsWTMWLFhg0B4ZGcnPP//M0KFDsbDI+beZjY0N3377LU2aNGHjxo3s27fP2CULIYQwkXM37rFkbwgAdtYWTOxU3bQFIUFIGMGIESM4ePAgJ06c0LctXbqUtLQ0hg4dmuv9aTQaRo4cCcDmzZuNVqcQQgjT0WoV7288Qfr9HtKvBFXE1930cyFKH6F81vnrf7l9L9nUZWSrhLMtm19t8cT76d27N2PHjmXBggV88cUXACxYsIB27drh7++fp33WrVsXgHPnzj1xfUIIIUxv3aEwgw7SI1uVN3FFOhKE8tnte8ncuJtk6jLylaOjI3379mX58uXMnDmT4OBgTp8+zZQpU/K8z4yO17GxsUaqUgghhKlEx6fw0e9n9MvTu9bEztp0HaQfJEEon5VwtjV1CQ9lzNqGDx/ODz/8wMaNG9myZQteXl507do1z/u7e/cuAK6ursYqUQghhIl8/PtZohN0Tw93rlOaFpW8TFxRJglC+cwYt56KgsaNG1OzZk2++uorjh49yqhRo7Cxscnz/o4cOQJA1apVjVWiEEIIEwgOiWJ18DUAnG2tmNixmokrMiSdpYXRDB8+nN27dxMfH8/w4cPzvB+lFPPnzwegU6dOxipPCCFEAUtN1/L+xpP65TefqYy3i50JK8qqUAeh8+fPM3nyZJo1a4a3tzdOTk7UqlWLd999l+jo6CzbJyQkMGHCBAICArC1tSUgIIAJEyaQkJBgguqLn0GDBjF58mQ+//xzatSokad9pKSk8Morr7Bv3z66detGkyZNjFylEEKIgrJ4dwhnb9wDoGYZFwY2DTBtQdko1LfGFi5cyJw5c+jcuTN9+/bFxsaG7du38/HHH7Ny5UoOHDhAyZIlAUhPT6dDhw7s3LmTgQMH0qpVK06cOMGsWbPYv38/f/75J5aWhaNjlrny8PDIVQfpP/74g5CQEJRS3L17l1OnTrFx40YiIiLo3Lkzy5Yty79ihRBC5KvrMYl8/ud5ADQa+KBbLSwtNCauKqtCHYR69erFhAkTcHNz07e9+OKLVKpUiQ8++IBZs2bx6aefArBkyRJ27tzJq6++yldffaXfvnz58rz++ussXbo0T2PaiPwzffp0ACwtLXF2dsbf35+OHTvSr18/2rRpY+LqhBBCPIlpm0+TkJIOQP/GZanj52bagh5Co5RSpi4it44fP06dOnVo3749W7ZsASAoKIidO3cSEhJiMHZNUlISXl5eNGzYkO3bt+f6WIcPH6Z+/focOnSIwMDAbLdJT0/n/PnzVK5cWa46iUJDvi+FEKay/ewthi4+CICXkw1/vRmEq711gdaQk89vKOR9hB4mPDwcAG9v3fwkSimCg4MpXbp0lgH87OzsCAwM5ODBgxTBzCeEEEIUKYkp6UzalNlB+n8dqxV4CMqNQn1rLDvp6enMmDEDgCFDhgAQFRVFfHz8Qzvo+vr68s8//xAdHY2Hh0e220RERBAREZGl/cyZM9lsLYQQQojszNl+gWtRiQA0Le9Jt7plTFzRoxW5IDR27Fj27NnD6NGjadu2LYD+qTBb2+wHCLSzs9Nv97AgNG/ePKZOnZoPFQshhBDFw9kbd5m38zIA1pYapneriUZT+DpIP6hIBaH333+fb775hh49ejBnzhx9u4ODbtK25OTs5/RKTEw02C47o0ePpkuXLlnaz5w5w4ABA56kbCGEEMLspWsVE9afIO3+pKovta5ARW8nE1f1eEUmCE2ZMoUPPviA7t278+OPP2JllVm6h4cHDg4OhIWFZfva8PBwHB0dcXd3f+j+fXx88PHxMXrdQgghRHGwfF8oR6/FAFC+hCMvt6lo2oJyqEh0lp46dSpTp06lZ8+erFmzBmtrw05XGo2GBg0acP36dUJDQw3WJSUlcfjwYRo0aFDoL88JIYQQRdH1mERmbjmrX/6oe61CM6nq4xT6IDRt2jSmTJlC7969s1wJetDAgQMB+Oyzzwzav//+e+Lj4/XrhRBCCGE8Sikm/XyS+PtjBr3QyI/G5T1NXFXOFepbY9988w2TJ0/Gz8+PDh068OOPPxqsd3Jyolu3bgAMHTqUpUuX8vXXXxMbG0urVq04fvw433zzDS1bttQ/YSaEEEII4/n95A3+PHMLgBLOtkx4rnBNqvo4hToIHTyoG4zp2rVr2QYZf39/fRCytLTkt99+Y9q0aaxevZpVq1bh4+PDuHHjmDRpkgwoJ4QQQhhZbEIqkzed0i9P7VKjUI8ZlJ1CHYQWL17M4sWLc7y9k5MTM2fOZObMmflXlBBCCCEA+HjLGW7f0z2x3a6aN8/VLGXiinKv0PcREkIIIUThs//yHVYduAaAo40l07oW/jGDsiNBSAghhBC5kpSazrs/ndAvv/1sVUq72ZuworyTICRM6sSJE7Rr1w53d3c0Gg1Tpkwxyn4XL16MRqNhx44dRtmfEEKITN9uv8jl2/EA1CvrxoAm/o95ReFVqPsICfOWlpZGjx49SE5OZvr06bi5uVG7dm1TlyWEEOIRzt64y3c7LwFgZaHhox61sLQoerfEMkgQEiZz+fJlLl68yOzZsxkzZoypyxFCCPEYaelaxq89Tmq6bhqNF1tXoGopFxNX9WTk1pgwmRs3bgA8cuoTIYQQhcf3/1zmRHgsABW9nXj1qaIxjcajSBASeZLRB+evv/7iww8/pHz58tjZ2VGnTh1+//13AE6fPk2nTp1wdXXF1dWVQYMGce/ePQACAgJo3bo1oBsMU6PRoNFoCAkJMThG8+bNcXFxwcHBgapVq/Laa6+RkpKS57pTU1P5/PPPqV+/Po6Ojjg7O1O7dm0mT578yNfdvXsXBwcHnn766WzXz58/H41Gw4oVKx65n5CQEH1fqNWrV1OvXj3s7OwoXbo048aNIz4+3mD76Ohoxo8fT6VKlbC3t8fV1ZXq1aszbty43L1xIYR4Qhdv3eOLPy8AYKGBT3vVxtaq6I/RJ7fGxBOZMGECKSkpvPTSS1haWvLVV1/RtWtX1q1bx/Dhw+nduzedO3dm7969LFmyBFtbW3744Qe++OILDh48yIcffsioUaNo2bIlACVKlABg8ODBLF26lMDAQN5++21KlCjBpUuX2LBhA9OmTcPGxibXtaampvLcc8/x119/ERQUxOTJk3FycuLs2bOsXbuWqVOnPvS1Li4u+gl/w8LC8PX1NVi/ZMkSXFxc6NGjR45q2bx5M7Nnz+bll19mxIgR/Pnnn3z++eccO3aMbdu2YWGh+z9K79692bFjByNHjqRevXokJydz8eJF/v7771y/fyGEyKt0rWL8uuOkpGkBGNGyPPXKmsnVfCUe6dChQwpQhw4deug2aWlp6vTp0yotLa0AKzOtRYsWKUDVrl1bJSUl6duPHTumAKXRaNTq1asNXtO1a1dlbW2t7t27p5RSavv27QpQixYtMthu7dq1ClC9evVSqampBuu0Wq3SarU5rm/79u36tk8//VQB6o033siyfXp6+mP3+ccffyhAffDBBwbtFy9eVIAaOXLkY/dx5coV/fnZv3+/wbpXXnlFAWrZsmVKKaViYmKURqNRL7300mP3m53i+H0phMgfP+y6pPzf+UX5v/OLCvp0u0pMKfy/V3Ly+a2UUnJFKL/Naw1xt0xdRfacvGH0zifaxZgxY7C1tdUv165dGxcXF5ycnOjdu7fBtq1bt+bnn38mJCSEmjVrPnSfy5cvB2DWrFlZJtl9ksG6li9fjqOjI9OnT8+yLuMKzKM89dRT+Pn5sWTJEt577z19+5IlSwByNZ/d008/TaNGjQza3nvvPb755hvWr1/PgAEDcHBwwNbWln379nH58mXKly+f4/0LIYSxhETGM+uPcwBoNDCzV+0iM7N8TkgQym9xt+DedVNXkW+y+3B2d3fHz88v23aAO3fuPHKf58+fx93dHX//R49Lcfv2bdLT0w3aSpV6+PDu58+fp2rVqjg6Oj5yvxmduDNYWlpSokQJLCwsGDhwIB9++CH79u2jSZMmKKVYtmwZlStXplmzZgDExcURFxdnsA9XV1fs7TMHG6tevXqW45YuXRpXV1cuXrwIgLW1NXPmzGHMmDFUqFCBypUr07JlSzp06EDXrl1l/jwhRL7TahVvrz9OUqrultjgpgE0DPAwcVXGJUEovzl5m7qChzNCbQ/7MH7Uh7RS6pH7fNz6DA0bNiQ0NDRPr30UHx8fg2V/f399J+4hQ4bw4YcfsnjxYpo0acKOHTsICQnhww8/1G8/a9asLP2NFi1alOMrRg9e9Ro+fDidO3fmt99+Y9euXWzbto0FCxbQqFEjdu7ciZ2dXd7epBBC5MDy/aEcuBIFgJ+HPW8/W8XEFRmfBKH89oS3noqjKlWqcPbsWUJDQx95VWjFihUkJibmeL+VK1fm/PnzxMfHP/Kq0LZt2wyWH7ySU6lSJZo1a8bq1av58ssvWbJkCRYWFgwaNEi/zaBBg2jRooXBPmrUqGGwfPr06SzHvX79OrGxsVSoUMGg3dvbmyFDhjBkyBCUUrz99tvMmjWLtWvXMnDgwMe/cSGEyINrUQl8/PtZ/fInPWrjYGN+scH83pEo8gYMGMDPP//MW2+9xY8//pjl6pJSCo1GQ/PmzXO93/HjxzNx4kRmz55tsE6r1er7CbVr1+6R+xkyZAijRo1i5cqVrF+/nqeffpoyZcro15cvX/6x/Xm2bdvGgQMHDPoJZVxVynjyLCEhAQAHBwf9NhqNhsDAQACioqIeeQwhhMgrpRQTNhwnIUXX/aBf47I0q+hl4qryhwQhUej06tWL/v37s2LFCho1akSPHj3w9vbmypUrrF27loMHD+Lm5pbr/Y4dO5Zff/2Vzz//nCNHjtChQwecnZ05f/48f/zxBydPnszRfvr06cPYsWN5/fXXiYuLy1Un6Qz16tWjXbt2vPzyy5QtW5Zt27axceNGWrduTb9+/QBdn6ZWrVrRrVs3atasiZeXF5cuXWLu3Lm4urrSvXv3XB9XCCFyYtWBa+y+qOvPWdrVjnefq2riivKPBCFRKC1btoxWrVoxf/58/ZWSsmXL0qlTJ4MrJLlhbW3N1q1b+eKLL1i+fDmTJk3C2tqacuXK8fzzz+d4PxnjBa1YsQI3Nze6deuW61o6d+5MtWrV+Oijjzh79izu7u6MHTuWGTNm6K+A+fn5MWLECHbs2MEvv/xCQkICPj4+dO3alQkTJlC2bNlcH1cIIR7n6p0EZvyaefv+o561cbazNmFF+UujjNG71IwdPnyY+vXrc+jQIf0tif9KT0/n/PnzVK5cWZ7kEY8UEhJCuXLlmDx5MlOmTMnXY8n3pRAit7RaRd8f9uk7SPdt6MfHPYvmZNg5+fwGmWJDCCGEEPct3H1FH4J83e15v1PWoT7MjQQhIYQQQnDh5j1mbj2nX571fB2cbM2/B40EISGEEKKYS03X8ubaY/q5xIY1L0eT8p4mrqpgmH/UE6IQCQgIMMqgj0IIYUzfbr/E8bBYACqUcDTLgRMfRq4ICSGEEMXYibBYvv77AgCWFhpm965rVnOJPY4EISGEEKKYSkpNZ9yao6RpdVeqXwmqQB0/N9MWVcAkCAkhhBDF1OfbznPhlm6S6BqlXRjTtpKJKyp4EoSMIGOSTOn7IQqTjO/HBydxFUKIDAdDovj+n8sA2FhaMLt3XWysil8sKH7vOB9YWFhgaWlJUlKSqUsRQi8pKQlLS0v9HGpCCJEhLjmNN9ccI+P/7+OeqUyVUs6mLcpE5KkxIylRogTh4eGUKVMGOzs7+V+4MBmlFElJSYSHh+Pt7W3qcoQQhdC0zae4GqWb2LmBvzsjWz56omhzJkHISNzd3QG4fv066enpJq5GFHeWlpZ4e3vrvy+FECLDlpMRrAkOA8DRxpLPetfB0qL4/uddgpARubu74+7ujlarlf5CwmQ0Go3cDhNCZOtGbBITNpzQL0/uUgN/T0cTVmR6EoTygXwICSGEKGy0WsVba48Rk5AKwHM1S/F8fV8TV2V68okthBBCFAMLd1/h34uRAJR0seXD7rWkPysShIQQQgizdybiLjO3ZE6o+tnzdXF3tDFhRYWHBCEhhBDCjCWlpvP6j0dJSddNqDqiRTlaVPIycVWFhwQhIYQQwox9suUs527eA6BqKWfGF6MJVXNCgpAQQghhpnadv82i3SEA2FhZ8NUL9bC1Kj4TquaEBCEhhBDCDEXFp/Dm2mP65Xefq0rlksVz9OhHkSAkhBBCmBmlFO+sP87te8kAtKpcgiHNAkxbVCElQUgIIYQwM0v3hrLt9E0A3B2smdWrtjwq/xAShIQQQggzcup6LB/8eka//FnvOni72JmwosJNgpAQQghhJuKT03h11RH9o/LDmpejbdWSJq6qcJMgJIQQQpiJyZtOcfl2PAA1y7jwznPyqPzjSBASQgghzMDGI+GsO5Q5q/zXLwTKo/I5IEFICCGEKOJCIuP530+Zs8rP6F6Tcl7Fe1b5nJIgJIQQQhRhyWnpjFl1mPiUdAB6BvrSvZ7MKp9TEoSEEEKIImzmlnOcDL8LQHkvR6Z1rWHiiooWCUJCCCFEEfX32Zss+PcKADaWFnzdrx6OtlYmrqpokSAkhBBCFEE3YpN4a+1x/fJ7HapSo7SrCSsqmiQICSGEEEVMWrqW11YdISo+BYB21UoyWKbQyBMJQkIIIUQRM+uP8xwIiQKgtKsdn8oUGnkmQUgIIYQoQv46c5O5Oy8BYGWh4et+gbg72pi4qqJLgpAQQghRRFyLSmDcmmP65Xc7VKO+v7sJKyr6JAgJIYQQRUByWjpjVh4mNjEVgGdrlGJY8wDTFmUGJAgJIYQQRcBHv53lWFgsAGU9HJj5vPQLMgYJQkIIIUQh9+vxCBbvCQHAxsqCb/sH4mJnbdqizIQEISGEEKIQu3w7jnfWZ44XNLlzdWqWkfGCjEWCkBBCCFFIJaWm8/KKw8QlpwHQrW5p+jUqa+KqzIsEISGEEKKQmvzzKc7euAdARW8nPuheS/oFGZkEISGEEKIQWn3wKquDrwFgb23Jd/0DZR6xfCBBSAghhChkjofFMPHnU/rlD7rXpFJJZxNWZL4kCAkhhBCFSFR8Ci8tP0xKmhaAgU386RHoa+KqzJcEISGEEKKQSEvX8uqqw4THJAIQWNaNiZ2qm7gq8yZBSAghhCgkZv1xnt0X7wDg5WTLdwPqY2MlH9X5Sc6uEEIIUQj8fiLCYDLVb/sHUtLFzsRVmT8JQkIIIYSJXbx1j7fWZk6m+l6HajQq52HCigpIxHG4cdKkJUgQEkIIIUzoXlIqo5cdIj4lHYCudUsztDhMphobBiueh4XPwsU/TVaGBCEhhBDCRJRSjF97nEu34wGoWsqZj3oUg0ETk2J1ISjuBqTcg39mg1ImKaXQB6GPP/6YPn36UKlSJSwsLLCyevhgUosXL0aj0WT71atXrwKsWgghhHi8uTsvs+XUDQCc7ayYO6A+DjZmPmhieiqsGQy3TuuWPcpD72VgovBX6M/2u+++i5ubG/Xq1SMuLo7bt28/9jXvvfce1apVM2jz9/fPrxKFEEKIXNt5/jafbj2rX/6yb10CvBxNWFEBUAo2j4XL23XL9h7Qfx04epqspEIfhC5evEiFChUACAoKylEQevrppwkKCsrnyoQQQoi8uXw7jjErD6O9fzdo7FOVaFu1pGmLKgjbP4SjK3R/t7SFvivBs4JJSyr0t8YyQlBuxcXFkZKSYuRqhBBCiCdzNymVEUuDuZekm1H+meolGftUJRNXVQAOLYZdM+8vaKDnD+Df1JQVAUUgCOVF165dcXZ2xtbWlurVq/Pdd9+hTNQJSwghhMiQrlW8/uNRLt/vHF25pBOz+9TFwsLMO0ef3wq/jMtcfvYjqN7VdPU8oNDfGssNBwcH+vbty1NPPUWpUqUIDQ1l3rx5vPzyyxw7doy5c+c+9LURERFERERkaT9z5kx+liyEEKIYmfXHOf4+ewsANwdr5g9qiJO5zygffgjWDgGlGx6ApmOgyUsmLelBZnX2e/fuTe/evQ3aRo8eTVBQEPPmzWPo0KE0btw429fOmzePqVOnFkSZQgghiqGfj4bz3Q7dyNGWFhq+6RdIWU8HE1eVz6Iuw4rekJqgW67RA56ebtqa/sOsglB2rKysePfdd+nUqRO//fbbQ4PQ6NGj6dKlS5b2M2fOMGDAgPwuUwghhBk7ERbL2+uO65ff71iN5hW9TFhRAYiPhOU9ISFSt+zfArrPBYvC1SvH7IMQQEBAAMAjnzjz8fHBx8engCoSQghRXNy+l8yoZcEkp2kB6N3AlyHNAkxbVH5LSYBVfXVXhABKVIW+y8HK1rR1ZaNwxbJ8cuHCBQBKlSpl4kqEEEIUJ8lp6by0/BARsUkA1Pd3Z3q3muY9crQ2HdaPgLCDumVnH91YQfbupq3rIcwqCN28eTNLW0JCgr7vT+fOnQu6JCGEEMWUUorJP58iODQagFIudnw3IBBbK0sTV5aPlILfxsO5X3XLNs7Qfy24+Zm2rkco9LfGli1bRmhoKAChoaEopZgxY4Z+/fvvv6//e82aNWnZsiX169enZMmSXL16lSVLlnD16lXGjx9PvXr1Crx+IYQQxdOi3SH8ePAaALZWFnw/qD7eznYmriqf/fs5BC/Q/d3CCvosg1K1TFvTYxT6ILRgwQJ27txp0DZx4kT93x8MQoMGDWLnzp3s2rWL2NhYnJ2dqV+/PrNnz6Znz54FVrMQQoji7e+zN5nx62n98sxetant62a6ggrC4aXw1wNPX3f9Biq0MV09OVTog9COHTtyvO1nn32Wf4UIIYQQOXAm4i6vrjyinz7j1bYV6Vq3jGmLym9nf9XNIZbhqclQp6/p6skFs+ojJIQQQpjSrXtJjFgSTHyKbvDAjrV8eKNdZRNXlc9CdsO6YaB0T8XR5GVo8YZpa8oFCUJCCCGEESSlpjNq6SHCYxIBqOPnxme965j39Bk3TsKqFyBN91QctXrDMx9AEXoqToKQEEII8YS0WsWba49x9FoMAKVd7fhhUH3srM34CbHoEFjeA5JjdcsV20G3bwvdgImPU7SqFUIIIQqhL/66wK/HdfNVOthYMn9wQ/N+QizuNizrDnH3h60p0wB6LwVLa9PWlQcShIQQQognsPFIOF/9pRu4V6OBr/rWo3ppFxNXlY+S7sKKnpmjRntV0Y0VZONo2rrySIKQEEIIkUeHQqMM5hD7X4dqtKte0oQV5bO0ZFjdHyKO6ZZdysDADeDgYdq6noAEISGEECIPrkUlMGrpIVLSdU9LvdCoLMNblDNxVflImw4bRsKVXbple3cY+BO4+pq2rickQUgIIYTIpZiEFAYvOsCd+BQAmlXwZFrXGuY7h5hS8NtbcPpn3bK1A/RbCyWqmLYuI5AgJIQQQuRCUmo6I5cGc/l2PAAVSjjyXf/6WFua8Ufq3zMgeKHu7xZW0HsZ+DU0bU1GYsb/akIIIYRxabWKt9Ye42CIbiJVLydbFg9thKtD0XtaKsd2fwn/zMpc7vYdVGpnunqMTIKQEEIIkUOfbD3LL/cfk7e3tmThkAb4eTiYuKp8FLwQtk3KXO4wC2r3Nl09+UCCkBBCCJEDS/eGMG+n7pFxCw3M6VfPvCdSPbEOfhmXudx2IjQaabp68okEISGEEOIxtp2+yZRNp/TL07rW5KlqZvyY/LnfYcMo4P7Msc3HQss3TVpSfpEgJIQQQjzCsWsxvLrqsH42+RdbV2BAE3/TFpWfLu+ENYNB6SaOpf5QaDe1SM0flhsShIQQQoiHuHongeFLDpKUqhsrqEud0rzdvug/Mv5QYcG6SVTTk3XLtZ6Hjp+ZbQgCCUJCCCFEtqLjUxiy+ACRcbqxghqX8+DT52ub72zyN07C8p6QqhsWgMrP6Z4QszDjiWORICSEEEJk8d+xgip6O/H9wAbYWplpKLhzSTeJalKMbjmgJTy/uEhOoppbOQ5ClpaWT/w1bdq0/HwvQgghxBNLS9cyZuURgkMzxwpaNKSh+Y4VFBsGS7tC/C3dcpkG8MIqsLYzbV0FxCqnGyql8Pf3JyAgINcHUUqxa9euXL9OCCGEKEhKKf7300n+PHMTACdbKxYPbWi+YwXdu6kLQbHXdMveNXQzyds6m7auApTjIAQwdOhQJk2a9PgNs2FhIXfhhBBCFG6f/XGe1cG6UGBjacH3A+tTs4yriavKJ/GRsLQL3LmoW/Yor5tEtQjPJJ8Xkk6EEEIIYPHuK8zZrgsFGg3M7lOHZhW9TFxVPkmIgqXd4PZZ3bKrHwz6GZzNeGykh8jxFaHbt2/j4JD3S4NP+nohhBAiv2w+dp2pv5zWL0/pXINOtUubsKJ8lBij6xh984Ru2bk0DN4MbmVNWpap5DgIeXp6PtGBnvT1QgghRH7490Ik49YcRd0fMHFMm4oMbhZg0pryTfI9WNELIo7qlp1K6kKQRzmTlmVKubo19ttvv6EyvlOEEEKIIu5keCyjlwWTmq77bOvTwI83n6ls4qrySUo8rOgNYQd1yw6eMGgTeFU0bV0mlqsg1KlTJwICApg6dSrXrl3Lr5qEEEKIfBcSGc+QRQeIT9FNJdGuWkk+6F4TjTmOopyaCKv6wtU9umU7N12fIO+qJi2rMMhVEGrdujVhYWFMmzaN8uXL06lTJzZt2oRWq82v+oQQQgiju3UviUELM0eNbhjgzpx+9bCyNMNniNKSYfUAuHJ/GBtbVxi0EUrVMmlZhUWu/sW3b9/OhQsXePvttylRogS//fYb3bt3x8/Pj4kTJ3LlypX8qlMIIYQwipiEFAbOP8DVqAQAKpd0Yv6ghthZm+Go0WkpuglUL/6pW7ZxggHroXQ909ZViOQ6+pYvX56PPvqIa9eusWHDBp599llu3brFBx98QKVKlWjfvj3r168nLS0tP+oVQggh8iwuOY0hiw5y7uY9AMq42bNkWCPzHDU6PQ3WD4fzv+uWrR10gyX6NTRtXYVMnq8BWlpa0q1bN3799VdCQkKYMmUKvr6+bNu2jd69e+Pr68uECRO4cOGCMesVQggh8iQpNZ1RS4M5ei0G0E2dsXxEY3xc7U1bWH5IT4OfRsGZTbplKzt44Ufwb2baugoho9wMLVOmDJMmTeLKlSv8/vvv9OjRg+joaD799FOqVatmjEMIIYQQeZZ6f/6wPZfuAOBiZ8Wy4Y0o5+Vo4sryQXoabBgJJ9frli1toO8KKN/atHUVUrmaYuNxNBoN7dq1Iz4+nrCwMPbv32/M3QshhBC5ptUqxq89pp8/zMHGksXDGlHNx8XEleWD9DTYMAJO/aRbtrSBPsuhYjvT1lWIGS0IXbhwgfnz57N06VJu3bqFUopy5coxfPhwYx1CCCGEyBWlFJM3nWLj0euAbv6wHwY1ILCsu4krywfpqbB+BJzeqFu2tIE+K6DyMyYtq7B7oiCUnJzM2rVrmT9/Pv/88w9KKaytrenRowcjR47kmWfk5AshhDCdT7eeY9m+UAAsLTTM6VeP5uY4f1h6qq5j9OmfdcsSgnIsT0Ho6NGjzJ8/n5UrVxIbG4tSigoVKjBixAiGDh2Kt7e3sesUQgghcuW7HZf4dscl/fKs52vzTI1SJqwon2QJQba6PkGVnjZtXUVEroLQvHnz+OGHHzhy5AhKKWxsbHj++ecZNWoUbdu2za8ahRBCiFxZsT+UT7ac1S9P71qD7vV8TVhRPklPhXXDMp8Os7SFviuhkvQJyqlcBaGXXnoJgMqVKzNy5EgGDx6Ml5cZXmIUQghRZK07FMb7G0/ql8e3r8LApgGmKyi/SAgyilwFoRdeeIFRo0bRurU8gieEEKLw2XTsOm+vO6afSX50q/K8HFTBtEXlh/RUWDcUzmzWLVvawgsr5emwPMhVEFqxYkW27XFxcVy4cIG4uDhatmxplMKEEEKI3NhyMoI3Vh9Fez8EDW7qz4TnqprfJKppKboQdPYX3bKEoCfyRAMqhoWF0aNHD9zd3WnQoAFt2rTRr/v333+pXr06O3bseNIahRBCiEf668xNXl11hPT7KeiFRn5M7lzD/EJQahKsGZgZgqzs4IVVEoKeQJ6DUEREBI0bN2bTpk107tyZpk2bojKuRQKNGzfm1q1brF692iiFCiGEENnZef42Ly0/TGq67jOoZ6AvH3SrhYWFmYWglARY1RfOb9EtW9np+gRVfMq0dRVxeQ5CU6dO5datW/z5559s2LCBp582fEzP2tqali1bsnv37icuUgghhMjOnkuRjFoaTEq6FoAudUozs1dt8wtByfdgRS+4vF23bO2om0BVQtATy3MQ+u233+jSpQtBQUEP3aZs2bJcv349r4cQQgghHupgSBTDFweTnKYLQc/VLMXs3nWwNLcQlBgDy7pD6P0LC7YuMHADlGtl0rLMRZ5Hlr558yaVKlV65DbW1tbEx8fn9RBCCCFEto5cjWboooMkpqYD0K6aN1/2rYeVpVHmEi88EqJgWTeIOKZbtnODgT9BmUBTVmVW8hyEPDw8CAsLe+Q258+fp1QpMxzFUwghhMmcCItl0MIDxCWnAdCqcgm+6R+IjZWZhaC4W7C0K9w6rVt28IJBG6FULZOWZW7y/F3TvHlzNm3axK1bt7Jdf+HCBbZs2WLwJJkQQgjxJE6GxzJw4X7uJelCULMKnnw/sD62VpYmrszI7l6HRR0yQ5BTKRjyq4SgfJDnIDR+/HgSExNp3bo1W7ZsISEhAYD4+Hh+//13OnfujIWFBW+++abRihVCCFF8nQyPpf/8/cQkpALQMMCd+YMbYGdtZiEo5ioseg7uXNAtu/jC0N/Au6pp6zJTeb411rhxY77//ntefPFFOnbsqG93cXHR7djKioULF1KjRo0nr1IIIUSxdiIslgEL9hObqAtBDfzdWTS0EQ42ef4YK5zuXIIlXeDu/a4n7gEwaBO4+5u0LHP2RN9BQ4cOpUWLFnz77bfs27ePO3fu4OrqSpMmTRgzZgxVqlQxVp1CCCGKqeNhMQyYv5+792+HNQzQhSAnWzMLQTdP654Oi7uhW/asBIM3gUtp09Zl5p74u6hSpUp8/vnnxqhFCCGEMPDfENQowIOFQxuaXwgKC4blPSEpRrfsXR0G/QxO3iYtqzgws+8kIYQQ5uLYtRgGLMjsGN2onAeLhjTE0dxC0OUdsKofpN4fbqZ0IAxYDw4eJi2ruDCz7yYhhBDm4Oi1GAbO38+9+4/INy7nwUJzDEFnftFNoJqeolsOaKmbO8zW2bR1FSNP9B0VFhbG559/ztGjRwkLCyM1NTXLNhqNhkuXLj3JYYQQQhQjR65GM2jBAX0IalJeF4LMrmP00ZXw8yugdCNjU6UD9FoE1namrauYyfN31c6dO3nuuedISkrC2toab29vrKyy7u7BiViFEEKIRzl8PwRlDJbYtLwnC4Y0ML8QtG8ubHknc7l2H+j6DVham66mYirP31njx48nPT2dFStW0KdPHywszGxETyGEEAUqOCSKIYsO6kNQswqeLBjcEHsbMxonSCnY+Qns+CizrdEoePYTkM9Rk8hzEDpx4gT9+vXjhRdeMGY9QgghiqE9FyMZviRYP3dY84qezB9kZiFIq4Wt78H+7zLbWr0Nbd4DjZlNFFuE5DkIubu74+7ubsxahBBCFEPbz95i9PJDpNyfRb5lJS++H9jAvEJQehpsfg2Orshsa/8hNH3FdDUJ4AmC0HPPPcfOnTuNWYsQQohiZsvJCF5ddYTUdF1/0qerl2ROv3rmNXdYaiKsHwFnf9Etayyg81cQONC0dQngCeYa++ijj4iOjuaVV14hPj7emDUJIYQoBn4+Gs4rKzNDUKfaPnzbP9C8QlBiDCzrkRmCLKzh+cUSggqRPF8R8vb2ZsuWLTRu3JilS5dSuXJlXF1ds2yn0Wj466+/nqhIIYQQ5mXNwWu8s+E4GQ8W96rvyyc9a2NpYUZ9Ze5e140WnTGDvLUj9F0OFdqati5hIM9B6NSpU7Rp04bY2FgAjhw5ku12GukAJoQQ4gFL9oQwedMp/fKAJmWZ1qUmFuYUgm6fh+U9IPaabtnBC/qvhTKBpq1LZJHnW2Pjxo3jzp07TJs2jdDQUFJTU9FqtVm+0tPTjVmvEEKIImzezksGIWhEi3JM72pmIejaQVj4TGYIcvOH4X9ICCqk8nxFaO/evfTo0YP333/fmPUIIYQwQ0opvvzrAl/8eUHf9lrbirzxdGXzunNwfiusGQxpibrlUrWg/3pwLmnausRD5TkI2djYEBAQYMRShBBCmCOlFB//fpZ5uy7r28a3r8IrbSqasKp8cGQFbHoV1P07IeVaQZ8VYOdi2rrEI+U5CAUFBXHgwAFj1iKEEMLMpGsV//vpBD8evKZvm9SpOsNalDNhVUamFPz7Ofw1NbOtRnfoPg+sbE1Xl8iRPPcRmjlzJqdPn+bjjz+W+cSEEEJkkZyWzqurDutDkEYDH/WoZV4hSKuFLRMMQ1Cj0dBzoYSgIiLPV4RmzJhBzZo1+d///scPP/xA3bp1H/r4/IIFC56oSCGEEEVLfHIaLy4/xD8XIgGwttTweZ+6dKpd2sSVGVFqEmx8EU79lNn21CRoMU6mzChC8hyEFi9erP/7lStXuHLlSrbbPWkQ+vjjjzly5AiHDx/m0qVLWFhYkJaW9tDtExISmDZtGj/++CMRERH4+PjQt29fJk2ahIODQ57rEEIIkTMxCSkMXXyQI1djALC3tmTuwPq0rlzCtIUZU0IU/NgPru7VLWssoctXUG+AaesSuZbnIPSw4GNs7777Lm5ubtSrV4+4uDhu37790G3T09Pp0KEDO3fuZODAgbRq1YoTJ04wa9Ys9u/fz59//omlpRmNWCqEEIXMrbtJDFxwgHM37wHgYmfFoqENqe/vYeLKjCjqCqzoBXcu6patHXSjRVdub9KyRN7kOQj5+/sbs46HunjxIhUqVAB0HbQfFYSWLFnCzp07efXVV/nqq6/07eXLl+f1119n6dKlDB06NN9rFkKI4ij0TjwDFuznWpTu0XEvJ1uWDW9ENR8zemoqLBhW9oEE3S0/HL2h32oZI6gIy3Nn6YKSEYJyYunSpQC8+eabBu2jR4/G0dFRv14IIYRxnb1xl15z9+pDkK+7PetebGpeIejsr7C4U2YI8qoCI/6UEFTE5fmKUGGjlCI4OJjSpUtnuVplZ2dHYGAgBw8eRCllXoN3CSGEiR0KjWboogPcTdL136xc0omlwxpTytXOxJUZ0f558Ps7wP2npANaQp9lYO9u0rLEk8vxFaHq1avz7bff5vlAT/r6x4mKiiI+Ph5fX99s1/v6+hIfH090dHS26yMiIjh8+HCWrzNnzuRbzUIIUdTtPH+bAfP360NQHT83Vo9qaj4hSKuFLe/B72+jD0G1nocB6yUEmYkcXxE6e/YskZGReT7Qk77+cRISEgCwtc1+3AY7Ozv9dh4eWTvtzZs3j6lTp2ZpF0IIkb0Nh8N4e91x0rS6gNC8oiffD2yAo62Z3GxITYQNI+HM5sy2lm9B2/fl8Xgzkqvv1h07duT5QPl9Oyrj0fjk5ORs1ycmJhps91+jR4+mS5cuWdrPnDnDgAHyOKQQQmRQSvH9rst89PtZfdtzNUvxRd+62FqZyZO58ZGw6gUIuz+DgsYSOs2G+kNMWpYwvlwHoScJQ/nJw8MDBwcHwsLCsl0fHh6Oo6Mj7u7ZX8r08fHBx8cnP0sUQogiT6tVfPDbGRb8mzmEysAm/kzpUgNLc5lB/vY5WNkbokN0yzZO8PwSqNTOpGWJ/JHjILR9+/YnPlh+TtKq0Who0KABu3btIjQ01KDDdFJSEocPH6ZBgwbSUVoIIfIoOS2dt9YeZ/Ox6/q2t56pzCttKprP79ZLf8OaIZAcq1t2KgX914BPHZOWJfJPjoNQ69at87MOoxg4cCC7du3is88+MxhH6Pvvvyc+Pp6BAweasDohhCi67iWlMnrZIfZcugOApYWGj7rXondDPxNXZkQH58Nvb2fOHl+qFrzwI7hm/xCOMA+FvkfbsmXLCA0NBSA0NBSlFDNmzNCvf//99/V/Hzp0KEuXLuXrr78mNjaWVq1acfz4cb755htatmzJkCFDCrp8IYQo8m7dS2LIwoOcjrgLgJ21Bd/2D6Rt1ZImrsxI0tPgj//B/rmZbVU6QI8fwNbJdHWJAqFRhXzq+KCgIHbu3PnQ9f8tPy4ujmnTprF69Wr9XGN9+vRh0qRJODnl/hv68OHD1K9fn0OHDhEYKINmCSGKl8u34xi86IB+oEQ3B2sWDmlIYFkzeXQ86S6sGwYXt2W2NXsN2k0BCzPp+F1M5fTzu9BfEcpt52wnJydmzpzJzJkz86cgIYQoJo5ei2HY4oNExacAUMbNniXDGlHR20yukkSH6qbLuH1/vDgLK+j0OQQOMm1dokAV+iAkhBCi4G0/e4uXVxwmMVXXX6ZqKWeWDGtESRczGSjx6n7d7PEZ02XYu0PvZVCupWnrEgVOgpAQQggDK/aHMunnU6TfHyixSXkPvh/UABc7axNXZiTH18LPr0D6/XHnPCtCvzXgmfO5LYX5kCAkhBAC0I0RNHPrOebuvKRv61jLh9l96pjHQIlaLez4EHZ9mtlWrhX0XirTZRRjEoSEEEKQlJrOW2uP8cvxCH3b6FbleefZqliYw0CJSXfhp9Fw7rfMtsDB0PEzsDSTK10iT/IlCK1evZq//vqLW7duodVqDdZt2rQpPw4phBAij6LjUxi5NJjgUN2k1BYamNqlBgObBpi2MGOJuqybLuP2/SlBNBbwzAxo8rLMGSaMH4TGjx/PF198QZs2bShdurT5jDYqhBBmKPROPEMWHeRKZDwA9taWzOlXj6eqmckYQZe2w9ohkBSjW7ZzhV6LoOJTpqxKFCJGD0JLly5l1apV9OrVy9i7FkIIYUSHQqMZuTRY/3h8CWdbFg5uSC1fVxNXZgRKwb7vdAMlqvt3JryqwAurpFO0MGD0IKTVaqlbt66xdyuEEMKIfj8Rweurj5KcpgsJlbydWDS0Ib7uDiauzAjSkuGXN+Doisy2ys9Bj+/BzsV0dYlCycLYOxw1ahTLly839m6FEEIYgVKK+f9c5uWVh/UhqFkFT9a91Mw8QtC9G7C4o2EIavkm9F0pIUhkyyhXhF577TX937VaLStWrGDbtm3Url0ba2vD3vgPToYqhBCi4KSla5n2y2mW7g3Vt/UILMPHPWpjY2X0/xcXvPBD8OMAuHddt2xlD92+gZo9TVuXKNSMEoROnDhhsJxxa+zs2bMG7dJxWgghTONuUiqvrjzCzvO39W1jn6rE6+0qmcfv5mM/wqbXMgdJdPGFF1aCTx3T1iUKPaMEoe3btxtjN0IIIfLBtagEhi85yPmbcQBYWWj4qEctnm/gZ+LKjCAtBba+Bwd/yGwr21Q3XYZTCdPVJYoMGVBRCCHMWHBIFKOWHdI/GebmYM13/evTtIKniSszgrsRsHYwXNuf2VZ/KDw3E6xsTFeXKFLyJQilpaVx4MABrl69SkpKisG6QYNkVl8hhCgIPx0J4511J0hJ13WKLl/CkYWDGxLg5WjiyowgdI9ufKC4m7plS1voOEtmjhe5ZvQgdPbsWTp37syVK1dQSmFpaUlaWhrW1tbY2tpKEBJCiHym1SpmbzvPnO0X9W3NK3rybb/6uDoU8ekklIL983TjA2nTdG0uvtBnGZQJNG1tokgy+mMCr7/+OvXr1yc2NhYHBwfOnDlDcHAwdevWZf369cY+nBBCiAckpqQzZtVhgxDUr3FZFg9tVPRDUEo8bBgJW97JDEHlWsPonRKCRJ4Z/YrQwYMH2blzJ46OjlhYWJCWlkZgYCAzZ87k1Vdf5fjx48Y+pBBCCODm3SRGLg3meFgsoJsz7P2O1RnaPKDoPxkWdVn3aPytU5ltzV+HthPBUrq7irwz+nePUgoHB92gXCVKlCA8PJwqVarg6+vLxYsXH/NqIYQQeXEyPJYRS4K5cTcJACdbK75+oR5tqnqbuDIjOL9VdyUoSRfwsHGCbt9C9a6mrUuYBaMHoZo1a3Ls2DHKly9Po0aN+OSTT7C0tOSHH36gYsWKxj6cEEIUe1tO3uCN1UdJTE0HoIybPQuGNKBqqSI+krI2HXZ+ovvK4FkJ+q6AElVMV5cwK0YPQv/73/+Ij9fNYjxjxgw6depEmzZt8PLyYs2aNcY+nBBCFFtareLrvy/y+Z/n9W2BZd34flADvJxsTViZEcTdhg0j4PKOzLaqnaDbdzJVhjCqXAWh4cOHM3LkSJo0afLQbdq3b6//e/ny5Tl9+jRRUVG4u7sX/XvUQghRSMQnp/HW2mP8fvKGvq1r3dJ80rM2dtaWJqzMCEL3wNqhEHf/vWksdH2BWrwB8jkijCxXQWjRokX4+/s/Mghlx8PDI1fbCyGEeLhrUQmMXBrM2Rv3AF02eOfZqoxuVb5o/4dTq4U9X8Ff00DpbvPhVBJ6LYSAFqatTZitJ741Nnv2bH777Tf+/PNPY9QjhBDiEfZciuSVFYeJTkgFwNnWiq/MoVN0QhRsfAnOb8lsK9cKei4ApyL+3kSh9sRB6N69ezLXmBBC5DOlFMv2hTJ182nStQqA8l6OfD+oARW9nUxc3RMKO6QbJTr26v0GDbQaD0ETwKKI3+YThZ4MviCEEIVcSpqWyZtOsurANX1b68ol+OqFerjaF+FBEpWCA9/D1v+BVneFC3sP6PEDVGpn2tpEsSFBSAghCrHb95J5afkhgkOj9W2jW5Xn7WerYmlRhPsDJd2FTa/C6Y2ZbX6Ndf2BXH1NVpYofnIdhIp0RzwhhChCTobHMmppMNdjdYMk2lpZ8EnP2nSrV8bElT2h60dg3TDdaNEZmo6BdlPAsghf4RJFUq6D0AcffMCmTZto1KgRjRo1IiwsLD/qEkKIYu3no+G8s/44Sam6meNLudjx/aD61PZ1M21hT0Ip2PctbJuceSvM1hW6fwdVO5q2NlFs5SoIPfXUUxw5coRDhw5x6NAh5s6dq18XFBREvXr1CAwMJDAwkGrVqmFhYfQ5XYUQwqylpmv5+PezLPj3ir4tsKwbcwfWx9vZzoSVPaH4SN1TYRf+yGwrHai7FeZRznR1iWIvV0Fo27ZtAFy+fJng4GD915EjR9i1axe7du3S3zqzs7OjVq1a1K9fn2+++cb4lQshhJm5fS+ZMSsPs/9KlL6tdwNfpneria1VEX566souWD8yc4BEgGav6QZJtLIxXV1CkMfO0uXLl6d8+fL07t1b33b+/HmDcHT06FEOHDjAwYMHJQgJIcRjHL4azcvLD+snTbW21DClSw36NSpbdPtmpqfBzo9h1yxA98g/Dl7QfZ48FSYKDaM9NVa5cmUqV65Mv379AN2YF2fOnOHQoUPGOoQQQpgdpRQrD1xlyqZTpKbrwkJJF1u+G1CfwLLuJq7uCcRcg/Uj4Nq+zLbyQboQ5FzKZGUJ8V/59vi8RqOhevXqVK9ePb8OIYQQRVpSajqTfj7JmuDMh04alfPgm36BlHAuwpOmntkMP78CSbG6ZY0ltH0fmr8O0ndUFDIyjpAQQphAeEwiLy0/xPGwWH3bsObleLdDVawti2hYSE2EP96Hg/Mz21zLQq8F4NfIdHUJ8QgShIQQooDtvhjJq6uOEBWfAoCdtW58oK51i/D4QBHHdB2iI89ltlXvCp2/Ans3k5UlxONIEBJCiAKilOL7XZf5ZMtZ7k8XRlkPB+YNrE81HxfTFpdX2nTY8zX8PSNzbCArO3j2Y6g/BIpqR29RbEgQEkKIAnA3KZV31h3n95OZj5AHVSnBl33q4epQREdTjg2Dn16EkH8y20rVhp7zoUQV09UlRC5IEBJCiHx2+vpdXl5xiJA7Cfq219pWZGy7ykV3vrAT6+DXcZkdotFA89egzfsyNpAoUiQICSFEPlpz8BoTfz5JcppuqgwXOytm965Lu+olTVxZHiXFwm/j4fjqzDYXX+g+F8q1NF1dQuSRBCEhhMgHiSm6R+PXHsp8NL5mGRe+618fPw8HE1b2BEL3wIbREHs1s61mT+j4GdgX4TGPRLEmQUgIIYzsSmQ8Ly0/xNkb9/Rt/RuXZWKn6thZF8GpMtJTYcdH8O/noHRXtrB10QWg2r0f/VohCjkJQkIIYUS/nYjg7XXHiUtOA8De2pKPe9Yquo/G3zyl6xB943hmW9lmulth7v6mq0sII5EgJIQQRpCSpuWj38+waHeIvq2itxPf9Q+kUkln0xWWV9p02PMVbP8Q0nXjHWFhBW3euz9CdBG8siVENiQICSHEE7oek8grKw9z5GqMvq1r3dJ82L0WjrZF8Nds5EXY+CKEHcxsK1ENun8HpeuZri4h8kER/AkVQojCY8e5W7yx+ijRCbrBBG0sLZjUuTr9GxfBWeO1WjjwPfw5BdIS7zfefyw+6D2wtjNldULkCwlCQgiRB6npWmZtPce8XZf1bb7u9nzbP5Davm6mKyyvokN1E6U+ODiiR3noNhfKNjZdXULkMwlCQgiRS9eiEnh11RGOXovRt7Wr5s1nz9cteqNEKwWHl8LW9yAlLrO90WhoNxlsHE1XmxAFQIKQEELkwu8nInh7/XHuJemeCrO21DDhuWoMax5Q9G6F3Y2ATa/CxW2Zba5+0PUbKN/adHUJUYAkCAkhRA4kpaYz49fTLN+XOZigv6cDX79Qr+jdClMKjiyHrf+D5NjM9noDof2HYFdEJ4AVIg8kCAkhxGNcvBXHmJWHDQZI7FynNB92r4mzXRG7FRYdCpvHwuXtmW1OJaHL11C5venqEsJEJAgJIcQjrDsUxsSNJ0lMTQfAztqCKZ1r0KehX9G6FabVQvAC2DYZUuMz22v3hWc/AgcP09UmhAlJEBJCiGzEJ6cxceNJNhwJ17dV8nbim/6BVC5qAyRGXtT1Bbq6J7PNpQx0+gIqP2OysoQoDCQICSHEf5wMj+W1VUe4HJl55aRvQz8md66BvU0RGlE5PQ32faMbHTotKbO9/lB4epr0BRICCUJCCKGn1Sq+/+cyn/1xjtR0BYCTrRUfdK9Z9OYKu3lKNy7Q9SOZbe4Bur5A5VqZrCwhChsJQkIIAUTEJvLmmmPsuXRH31arjCtfv1CPAK8iNJZOWrJulvhds0Cber9RA01ehrb/k3GBhPgPCUJCiGLv9xMRTNhwgthEXXDQaODF1hV4o11lbKwsTFxdLoTu0T0RFnk+s82rCnSdA36NTFeXEIWYBCEhRLEVn5zGtM2nWR18Td/m42rH7N51aVrB04SV5VJiNGybpBshOoPGElq8Dq3eljnChHgECUJCiGLp2LUYxv54hJA7Cfq2DrVK8WH3Wrg52JiwslxQCk6uhy0TIP52ZnvpQOj8JfjUNl1tQhQREoSEEMVKulYxd+clPt92njStrkO0g40lU7rU4Pn6vkVnbKDoEPhlHFz6K7PNxhmemgQNh4NFEXq6TQgTkiAkhCg2wmMSGbf6KPuvROnb6vi58WWfukWnQ3R6KuydAzs+gbTEzPaqneC5meBaxJ5uE8LEJAgJIcyeUor1h8OZuukU95J1k6VqNPBKUEXGtquEtWUR6RAdFqzrDH3zZGabc2noOAuqdjRdXUIUYRKEhBBmLTIumfc2nOCP0zf1baVd7fi8T10aly8iHaITo+Gv6RC8EFD3GzXQeDS0fR9si9hI10IUIhKEhBBma+upG7y34QR34lP0bT0CyzC5cw1c7YvAZKlaLRxbqXsiLCFzfCNK1dJ1hi5T33S1CWEmJAgJIczO3aRUpm46zfrDYfo2D0cbPuxei2drljJhZbkQcRx+ewuu7c9ss3aEoAm6wREt5de3EMYgP0lCCLOy52Ikb609xvXYzLm1nq5ekg+716KEs60JK8uhpFjd3GAHvgelzWyv3g3afyidoYUwMrMLQo969PXevXs4OTkVYDVCiIKSmJLOJ1vOsnhPiL7NydaKyZ2r06soPBavFBxfA3+8D/G3Mts9K0KHT6FCW9PVJoQZM7sgBNCyZUtGjRqVpd3OTkZXFcIcHbkazZtrj3H5duZs8U3Le/Lp87XxdXcwYWU5dPO07jZY6O7MNit7aD0emo4BqyJwJUuIIsosg1D58uUZMGCAqcsQQuSzpNR0PvvjHAv+vcL9sRGxtbLgnWerMqRZABYWhfwqUFIs7JwJ+74DlZ7ZXrUTPPsRuJU1XW1CFBNmGYQAUlJSSE5OxtlZHisVwhwdDIni7XXHuRKZeRWotq8rs3vXpaJ3Ib8FrtXC0eXw1zTDqTHcy+lug1V62nS1CVHMFJFRxHJn3bp1ODg44OLigqenJyNGjODmzZuPf6EQotBLSEljyqZT9J63Vx+CbO5fBdrwUrPCH4Ku7oMf2sCmVzNDkKUtBL0HL++TECREATO7K0INGzakV69eVK5cmbi4OLZt28bChQv5888/2b9/PyVLlsz2dREREURERGRpP3PmTH6XLITIob2X7vDO+uNcjcqcKLVeWTc+7VWbit6F/OpvbBhsmwwn1xm2V+8KT08Hd3/T1CVEMWd2QejAgQMGywMGDKBJkya8/PLLTJ06lW+//Tbb182bN4+pU6cWRIlCiFyKS07j49/PsHzfVX2brZUF49tXYWjzclgW5r5AqYmw+yv493PDucFK1oRnP4ZyLU1XmxACjVJKPX6zos/b2xt7e3tCQ0OzXf+oK0IDBgzg0KFDBAYG5neZQoj/+OfCbSasP0F4TGaIaBTgwSe9alOuME+UqhSc3gh/TITYa5nt9h66aTECB8ugiELko8OHD1O/fv3Hfn4Xm59Cf39/Tp069dD1Pj4++Pj4FGBFQohHiYpPYcavp9lwOFzfZm9tyTvPVmFQ00L+RFjEMdjyHoT+m9mmsYRGoyDoHbB3N11tQggDxSIIabVaLl++TKlSRWRofSGKMaUUG4+GM/2XM0Q9MEdY0/KefNKzNmU9C/G4QLHh8Pd0OPYjmZOjAuXb6G6DeVc1WWlCiOyZVRC6efNmtp2hP/74Y6Kiohg4cKAJqhJC5FTonXje33iSfy5E6tuc7ax497lq9G3oV3ivAiXfg3+/gL1zIC1zag/cy+nGA6r8LBT2ka2FKKbMKgh99NFH/PXXX3Tq1Al/f38SEhLYtm0bW7ZsoWrVqkyaNMnUJQohspGarmX+P1f44s/zJKdlzq/VsbYPkztVx9ulkI4Kn54Gh5fAjo8MxwOyc4PWb0PDETIqtBCFnFkFobZt23L27FmWLVtGZGQkFhYWVKhQgffff5+3335bBlcUohA6ei2GCeuPc/bGPX1baVc7pneryVPVsh/uwuSUgvNbYdskiDyX2W5hDY1HQ8s3wcHDdPUJIXLMrIJQly5d6NKli6nLEELkQFxyGrO2nmPJ3hAynl210MCQZuV485nKONoW0l9PEcd0E6Ne2WXYXr0btJsMHuVNUpYQIm8K6W8aIYS5Ukrx64kIpv9ympt3k/Xt1Xxc+LhHLer4uZmuuEeJDoXtH8Lx1Rh0hPZtBO0/AL9GJitNCJF3EoSEEAXm0u04Jv98in8vZnaGtrO24I12lRnWohzWloVw1p+42/DPZxC8ANIzn2LDPQDaTdWNDC0doYUosiQICSHyXUJKGnP+vsgP/1wmNT3zakqbKiWY2qVm4XwkPuku7P1G9yRYSlxmu50btH7nfkdoG5OVJ4QwDglCQoh8o5Tij9M3mbb5tMHI0GXc7JncuTpPVy+JprBdTUlNguCF8M8sSLiT2W7tAE1egmavgb2bycoTQhiXBCEhRL4IvRPP5E2n2HEu87Fya0sNo1qVZ0ybStjbWJqwumxo03UDIe74yHBKDAsrqD8EWo0HZxmUVQhzI0FICGFUiSnpzN15ie92XiLlgTGBWlbyYmqXGpQv4WTC6rKhFJz9VTci9O2zhutqPQ9t3pMnwYQwYxKEhBBGoZTil+MRfPTbGa7HZo6uXMrFjomdqtOhVqnCdRtMKbj4F2z/AK4fNlxX6RloOxF8apumNiFEgZEgJIR4YifDY5m6+RQHQ6L1bVYWGoa1KMdrT1XCqTCNCaQUXN4O2z+CsAOG6/waw1OTIaC5aWoTQhS4QvTbSQhR1Ny+l8ysredYc+iaflBEgFaVSzCpUzUqehey0dyv7NKNBXR1r2F7yZrQ5n9Q5Tl5FF6IYkaCkBAi11LStCzec4Wv/rpIXHKavr28lyPvd6pGmyrehes2WOgeXQAK+cewvUQ1aPMuVO0MFoVwDCMhRL6TICSEyDGlFH+fvcWMX89wJTJe3+5sa8XYdpUY1DQAG6tCFCiuHdD1Abq8w7DdqzIETYDq3SUACVHMSRASQuTIyfBYPvztDHsuZY6to9FA34Z+vPlMFbycCtEs69cOwM5P4OKfhu0eFXQBqGZPsChkj+8LIUxCgpAQ4pHCohOYtfUcG49eN2hvVM6DSZ2qU7OMq4kq+w+ldLe+dn2adUJU9wDdaNC1eoOl/NoTQmSS3whCiGzFJqTy7Y6LLNoTYjAekL+nA2+3r1p4HodXSnflZ9encG2/4TrXstB6PNR5ASytTVOfEKJQkyAkhDCQnJbOsr2hzNl+kZiEVH27m4M1r7WtxIAm/oWjH5BWC+d+1QWgiGOG6zzKQ8s3oXYfCUBCiEeSICSEADIHRJy59SzXojLnBbOxsmBo8wBeDqqIq30hCBXadDj1E+yaBbfPGK4rUQ1avQU1uksfICFEjkgQEqKYU0qx60Iks7ae40R4rL5do4HudcvwZvsqlHGzN2GF96Ulw/HV8O8XEHXJcJ1PHd1cYFU6ylNgQohckSAkRDEWHBLFzK3nOHAlyqC9eUVP3n2uWuHoCJ0UC8GLYN93EHfDcJ1fY10AqthOBkIUQuSJBCEhiqGT4bF89sc5tj8wMzxAdR8Xxj9bhaDKJUzfEfrudV34CV4EKfcM15VrpQtAAS0lAAkhnogEISGKkUu345i97Ty/Ho8waC/v5ci4ZyrToaYPFhYmDha3zsKer3W3wbSpD6zQQLVO0Gws+DU0WXlCCPMiQUiIYiAsOoGv/rrAukNhaB+YE6y0qx2vt6tMj8AyWFmasG+NUnB1H+z+Es7/brjO0hbq9oOmY8CromnqE0KYLQlCQpixsOgEvt1xibXB10hNz0xAXk42jGlTkRcal8XWyoRPV6Wn6R6B3zMn60zwdq7QcCQ0Hg1O3qapTwhh9iQICWGGrkUl8O2Oi6w7FGYQgFzsrBjdugJDmgXgaGvCH//EGDiyDPZ/D7FXDde5+ELTVyBwINgWstnrhRBmR4KQEGbk6p0Evtl+kfWHw0h74B6Yk60VQ5oFMLJleVwdTDgW0J1LsH8uHFkBqfGG67xrQPOxULOHDIIohCgwEoSEMAOhd+KZ8/dFNhwJJ/2BAORsa8XQ5gEMa1EONwcb0xSnlG7ur33fwvmtgDJcX6k9NHkJygfJE2BCiAInQUiIIuzy7Ti+3XGJn/4bgOysGNa8HMOalzPdFaDUJDixVvcI/K1ThuusHXQdoBu/CF6VTFOfEEIgQUiIIulkeCzf7rjI7ydvoB64wOJiZ8WwFuUY2ryc6abDiA6FQ4vg8FJIuGO4zqUMNBoF9QeDvbtp6hNCiAdIEBKiiFBKsffyHb7bcYl/LkQarHO1t2Z4i3IMaR6Ai50JApBWC5f+hoM/ZH/7y7eR7vZXtc7S/0cIUahIEBKikNNqFdvO3OS7HZc4ei3GYJ2Xky3DW5RjQJOyOJsiACVEwdEVcHABRF8xXGdhDdW76gKQb4OCr00IIXJAgpAQhVRqupZNR68zd+clLtyKM1jn52HP6FYV6FXfFztrE4wDFH4YDs6Hk+shLclwnYsvNBgCgYNl/B8hRKEnQUiIQuZeUiprgsNY+O8VwmMSDdZVLeXMS0EV6FjLp+BHgk6Jh5MbIHghXD+cdX35NtBopO4pMEv51SKEKBrkt5UQhUR4TCKLd1/hxwPXuJecZrCuYYA7LwdVJKhKAU+GqhRcPwKHl8CJ9VknP7V1hXr9ocFwmf5CCFEkSRASwsSOXYvhh38u8/vJGwaPwAO0qVKCl9tUpGGAR8EWlRije/T90BK4eSLr+lK1dVd/avYEG8eCrU0IIYxIgpAQJpCuVWw7fZMF/17mYEi0wTobKwt6BpZhWPNyVCpZgFNMKAVX9+rCz+mNWfv+WDtCrZ66vj9l6svgh0IIsyBBSIgCFJ+cxrpDYSzcfYXQOwkG6zwdbRjY1J8BTfzxcrItuKLu3YTjq3Xj/ty5kHV9mQa6cX9qdJe5v4QQZkeCkBAF4PLtOJbuDWX9obAs/X8qejsxokU5utUrU3BPgKUmwbnf4NgquPgXqHTD9XZuUKcvBA6CkjUKpiYhhDABCUJC5JN0rWLHuVss2RvKrvO3s6xvWcmL4S3K0apSCSwsCuA2k1IQdhCOroRTGyApNus2AS11t76qdQJr+/yvSQghTEyCkBBGFpOQwprgayzbF8q1KMPH322tLOhWtwxDmgdQzcelgAq6CsdW667+RF3Kut7FV3f1p24/8KxQMDUJIUQhIUFICCM5ff0uS/aEsPFoOMlpWoN1fh72DGziT+8GfgUzC3zSXTizWRd+Qv7Jut7aEap3gTov6K4CWRTwmERCCFFISBAS4gkkpabz6/EIVh64yqHQ6CzrW1byYkizAIKqeGOZ37e/UhN183ydXAfn/4D05KzbBLTUXfmp1gVsnfK3HiGEKAIkCAmRB+dv3mPl/qtsOBzG3STDzs9Otlb0qu/LwKb+VCiRz2EjPQ0u79CFnzO/ZB3wEMCjgu7KT50+4FY2f+sRQogiRoKQEDn0uKs/lUs6MbBpAN3rlcHJNh9/tLRauLZfF35ObYSEyKzbOJbQPe5e63nwbShj/gghxENIEBLiMc7duMeqA9lf/bG1sqBjbR/6Ny5LYFn3/Jv+QikIP6Qb6PDURoi9lnUbWxfdLa9aPSGglcz3JYQQOSC/KYXIRlxyGr8ev86a4LCHXv3p16gs3ev54upgnT9FZFz5Of0znNkEd8OzbmNlB5WfhVq9oOLTYG2XP7UIIYSZkiAkxH1arWLflTusCw7j95M3SEw1HGTQ1sqCTrVL06+xX/5d/UlPg6t77oefXyDuRtZtNJZQoa0u/FTpAHYF9Bi+EEKYIQlCoti7FpXAukNhrD8cRlh0Ypb1+X71Jy1F94j7mU268JNdnx8La6jQBqp31YUfhwKehFUIIcyUBCFRLCWkpPHbiRusO3SNfZejsqx3sbOiS93SPF/fj9q+rsa/+pMYAxf/1E1zceFPSM5mlGdLW6j4lC78VH4W7N2MW4MQQggJQqL4SNcq9l2+w8Yj4fx2IoL4FMNbXxYaaFmpBL3q+/J09ZLGn/crOgTO/a4LP6F7QJuWdRsre6j09P3w014mORVCiHwmQUiYNaUUp67fZeORcDYfv87Nu1kHGSzv5UivBr70qOdLKVcjdjbWauH6ETj3qy4A3Tqd/XZ2rlCpPVTtqAtBNo7Gq0EIIcQjSRASZunqnQQ2Hg3n56PhXLodn2W9k60Vnev40Ku+r3E7PidGw6W/dTO6X/wT4m5mv517AFTpCFWeg7JNwDKfnjwTQgjxSBKEhNm4E5fML8cj2Hg0nCNXY7Kst7bUEFTFm651S9OumpFufWm1EHFUF3ou/qmb3V1ps9lQA74NdMGnSgcoUVUGORRCiEJAgpAo0qLjU/jj9A1+PXGD3RcjSdeqLNs0KudBt7pl6FCrlHEmPI2/c/+qzzbdlZ/snvICsHaAcq2hagfdrS/nkk9+bCGEEEYlQUgUOdHxKWw9dYNfT0Sw59KdbMNP1VLOdK1bhi51S1PGzf7JDpiaBNf2weWdcGUnhB8Gsh4TAK8qun4+FdtB2aYywKEQQhRyEoREkRAVn8Ifjwk/Zdzs6VK3NN3qlqFKqSd42kqbDtePwpUduvBzbT+kJWW/rY2T7qpPpXa68COTmgohRJEiQUgUWrfvJfPnmZv89pjw06m2Dx1q+eR9vB+lIPJ85hWfkH8gKZtxfTJ4V9eFnkpPg18TsDLC7TYhhBAmIUFIFCqXb8fxx+mbbDt9k8NXo1HZ3IF64vCj1cLtM7qxfEJ36/582NNdAK5+UL41lAuCcq2kr48QQpgRCULCpLRaxdGwGLadvskfp25k+6g7gK+7PR1r5TH8pKdCxPHM0HN1LyTFPHx7ew9d4CnfWnfby6O8POElhBBmSoKQKHBJqensvXyHbfev/Ny+l3WQQ4BK3k48U6Mkz1QvlbvwkxwH1w/D1X264HPtAKRmH7AAsHGGso11oad8ayhZCyws8vDOhBBCFDUShESBuBaVwI5zt9h+7jZ7LkWSlJp1rB2NBhr4u/N09ZI8Xb0U5bxyMMKyUnDnom78nmsHICwYbp16yFg+99l7gH+zzK+StcBSfhSEEKI4kt/+Il+kpGk5GBLF9rO32H7u1kNvedlaWdCykhdPVy/JU9VK4uVk++gdJ8VC+CFd4Ll2AMKDdaM5P4pz6QeCT3PwqixXfIQQQgAShIQRXb2TwL8XI9lx7ha7L0ZmmdQ0QwlnW4Iql+Cpat60qlwCB5uHfBsmx8GN47pH2SOO6v6MPM9Dx/AB0FiAdw3dKM5+jXThx81f+vgIIYTIlgQhkWd34pLZc+kOuy9GsvtSJNeiErPdzkIDgWXdCapSgqAq3lT3ccHC4j/BJC+hB8DBE3wbZQaf0vVkxnYhhBA5JkFI5FhCShr7r0Sx52Ik/168w5mIuw/d1tPRhtZVStCmijctK3llTm2hFNy9DjdPwc2Tuj9vnMhZ6LGwhpI1wLeh7suvIbiXk6s9Qggh8kyCkHio2MRUDoVGceBKNAeu3OFEeCyp6dmHFRtLC+r7u9OikhctKnpRq4wrFmkJcOsMnNl2P/jcDz+PenQ9Q0boKV0XfOrq/vSuDlaP6UMkhBBC5IIEIaF3614SB++HngMh0Zy9cTfbAQ1BdxGmRmkXmlf0orW/LYEOkdjFXITbv8O/F3QDFkZd4bFXeeB+6Kmuu60loUcIIUQBkiBUTCWnpXP6+l2OXYvh6P2vkDsJj3iFor5HCs+VuksTlztU1ITrgs/p87A/IucHdi6tu9JTsjqUrKn7u2clmaZCCCGESZhlENqwYQMzZ87kxIkT2NjY0KJFCz744ANq165t6tJMQqtVhNyJ5+i1GH3wOR1xN8ttLgu0+HCHsha3KKe5QaBzDNXt7uCrbuCUcA2LhHi4nMODWtmDd7X7oed+4ClZAxw8jP8GhRBCiDwyuyC0YMECRowYQc2aNfnkk09ITk5mzpw5NG/enH///Zc6deqYusR8lZCSxtkb9zgTcZfT1+9yJuIuZ2/cIyElHVB4cpfSmju01URSxvIOfppblNXcopzFTfw0t7AmLXNnSfe/HsXeA0pU0Y3N41U58++ufjJWjxBCiELPrIJQTEwM48aNw9fXl927d+Pi4gJAnz59qF69Oq+99ho7d+40cZXGEZecxuXbcVy6Hcfl2/FcunWPqxG3SIwOx5toymgiKUMkfTR3KK2JpLTNHcpoIrHTpOb+YBZWurF4PMr9J/BUAUdP4785IYQQooCYVRDauHEjd+/eZdy4cfoQBODr60vv3r1ZsGABISEhBAQEmK7IHNJqFZFxyVy7E8ft2zeIuRVGQtR1kqPDSb97A4fkSLw10ZTRxFCPGLw1MThokiGvXW2s7HVBx6M8uAfo/sxYdvGVKSiEEEKYJbP6dNu/fz8AzZo1y7KuWbNmLFiwgAMHDpg8CN2LjSL83GESY2+SHBtJWtxtSLiDZVIU1snR2KXG4pAeizv3qEc8FppsnrzK7b+ctSO4+YGrr+62Vcafbn66sXicS8l4PEIIIYodswpCYWFhgO4K0H9ltGVs818RERFERGR9+unMmTNGrFDnyrF/qP33oEdvlItMkmrtjHIqiZWrDxbOpXShxtXvgcDjC/buEnSEEEKI/zCrIJSQoHv829Y26/gzdnZ2Btv817x585g6dWr+FfcARzfvx24ThyPxVq6k2LihtfPAwskLG7fSOHmVwdGzDDiVAueS4FQKaxuHAqhaCCGEMD9mFYQcHHSBIDk5Ocu6xMREg23+a/To0XTp0iVL+5kzZxgwYIARqwSv0gHsK9EbHDywdCqBjUsJHNxK4OxRClfPUti7eOFkZYOTUY8qhBBCiP8yqyD04O2vatWqGawLDw832Oa/fHx88PHxyd8C73P18qHJKz8UyLGEEEII8XBmNdBLo0aNANizZ0+WdRltDRs2LNCahBBCCFF4mVUQ6tatG87OzsyfP5+7dzNnRg8LC2PNmjW0aNGCcuXKmbBCIYQQQhQmZhWE3N3dmTVrFmFhYTRv3pw5c+Ywe/ZsWrZsiVar5auvvjJ1iUIIIYQoRMyqjxDAqFGj8PDw4NNPP+Xtt982mGvM3KfXEEIIIUTumF0QAujVqxe9evUydRlCCCGEKOTM6taYEEIIIURuSBASQgghRLElQUgIIYQQxZYEISGEEEIUWxKEhBBCCFFsSRASQgghRLElQUgIIYQQxZYEISGEEEIUW2Y5oKIxJSYmAnDmzBkTVyKEEEKInMr43M74HH8YCUKPERISAsCAAQNMW4gQQgghci0kJITmzZs/dL1GKaUKsJ4iJzIykq1btxIQEIC9vb3R9nvmzBkGDBjA8uXLqVatmtH2a47kXOWcnKvckfOVc3Kuck7OVc7l57lKTEwkJCSE9u3b4+Xl9dDt5IrQY3h5edG/f/9823+1atUIDAzMt/2bEzlXOSfnKnfkfOWcnKuck3OVc/l1rh51JSiDdJYWQgghRLElQUgIIYQQxZYEISGEEEIUWxKEhBBCCFFsSRAyER8fHyZPnoyPj4+pSyn05FzlnJyr3JHzlXNyrnJOzlXOFYZzJY/PCyGEEKLYkitCQgghhCi2JAgJIYQQotiSICSEEEKIYkuC0BP6+OOP6dOnD5UqVcLCwgIrq0cP1p2QkMCECRMICAjA1taWgIAAJkyYQEJCQrbbHz9+nM6dO+Pu7o6joyNNmjRhw4YN+fFW8tX58+eZPHkyzZo1w9vbGycnJ2rVqsW7775LdHR0lu2L63kC3bQuw4YNo06dOnh6emJnZ0f58uV54YUXOHbsWJbti/O5yk58fDzlypVDo9EwYsSILOuL+/nSaDQP/YqLizPYtrifK4C7d+/y/vvvU61aNezt7fHw8KBx48YsX77cYLvifK6mTJnyyO8rjUZDeHi4fvtCd66UeCKAcnNzU23atFGlSpVSlpaWD902LS1NtW7dWgFq4MCB6ocfflCvvfaasrS0VEFBQSotLc1g+6NHjyonJyfl6emppk+frubOnatatGihADV//vz8fmtG9c477yhHR0fVt29f9eWXX6rvvvtO9e7dWwGqbNmy6saNG/pti/N5UkqpCxcuqKZNm6px48apL7/8Us2fP1+9//77ytfXV1lbW6s//vhDv21xP1fZef3115WTk5MC1PDhww3WyfnS/c5q2bKlWrZsWZav1NRU/XZyrpQKCwtTlSpVUm5ubuqNN95Q8+fPV19++aV65ZVX1IwZM/TbFfdzdezYsWy/n2bMmKEAFRgYqN+2MJ4rCUJP6OLFi/q/t27d+pFBaMGCBQpQr776qkH7F198oQC1cOFCg/aWLVsqjUajDh48qG9LTU1VDRs2VK6urio6Oto4b6IAHDx4MNt6//e//ylAvfXWW/q24nyeHiUsLExZWlqqNm3a6NvkXBk6cOCAsrS0VLNnz842CMn50gWhwYMHP3Y7OVdKtW3bVpUsWVKFhIQ8cjs5V9l7//33FaDmzp2rbyuM50qCkBE9LghlpOD//lAlJiYqR0dHFRQUpG+7cuWKAgzaMixbtkwBavHixcYr3kSOHTumANW+fXt9m5yn7KWlpSknJydVt25dfZucq0wpKSmqdu3aqkuXLvr3+t8gJOcrMwglJyeru3fvPnS74n6u/v33XwWo2bNnK6V0P3/37t3Ldtvifq6yk5aWpsqUKaMcHR1VbGysvr0wnivpI1RAlFIEBwdTunRp/P39DdbZ2dkRGBjIwYMHUfeHddq/fz8AzZo1y7KvjLaMbYqyjPvG3t7egJynB6WmphIZGcmNGzc4cOAA/fv3Jy4ujo4dOwJyrv5r5syZXL58mTlz5mS7Xs5XpnXr1uHg4ICLiwuenp6MGDGCmzdv6tfLuYJff/0VgEqVKtG7d2/s7e1xdnamdOnSzJgxg/T0dEDO1cP8/vvvhIeH06dPH1xcXIDCe64kCBWQqKgo4uPj8fX1zXa9r68v8fHx+o7DYWFh+vbstn1wm6IqPT2dGTNmADBkyBBAztODdu/eTYkSJfDx8aFx48b8/vvvjB8/nsmTJwNyrh507tw5pk+fzvTp0/Hz88t2GzlfOg0bNmTSpEmsW7eOZcuW0alTJxYuXEjjxo31YUjOFZw5cwaA4cOHExoayvz581m6dCn+/v5MnDiRl156CZBz9TA//PADAKNGjdK3FdZz9ehHnITRZPSGt7W1zXa9nZ2dfjsPD49Hbm9jY4NGo3loD/uiYuzYsezZs4fRo0fTtm1bQM7Tg+rUqcO2bdtITk7m/PnzrFixgoSEBFJSUrC2tpZzdZ9SipEjR1KjRg1effXVh24n50vnwIEDBssDBgygSZMmvPzyy0ydOpVvv/1WzhVw7949ABwcHNi1a5f+vfXp04fq1aszf/583nzzTRwcHIDifa7+KyIigt9++41atWrRuHFjfXth/b6SK0IFJOOHJTk5Odv1iYmJBts9avvk5GSUUvptiqL333+fb775hh49ehjcypDzlMnd3Z127drRsWNH3njjDbZt28amTZvo2bMnIOcqw7x589izZw/ff/89lpaWD91OztfDvfTSS5QoUUJ/O0jOFdjb2wPQr18/gw9iGxsb+vfvj1KK7du3y7nKxqJFi0hLS2PkyJEG7YX1XEkQKiAeHh44ODg89DJeeHg4jo6OuLu7A4++7JfRr+ZhlxcLuylTpvDBBx/QvXt3fvzxR4Oxl+Q8PZy7uztdunRh69athISEyLkCYmNjmTBhAr1798bT05OQkBBCQkL07zEuLo6QkBBiYmLkfD2Gv78/t2/fBuTnENDfYs1uMtCMtqioKDlX/6GUYsGCBdjZ2TFw4ECDdYX1XEkQKiAajYYGDRpw/fp1QkNDDdYlJSVx+PBhGjRogEajAaBRo0YA7NmzJ8u+MtoytilKpk6dytSpU+nZsydr1qzB2traYL2cp0fL+B9TdHS0nCt05yE2NpZVq1ZRrlw5/VfLli0BWL16NeXKlWPWrFlyvh5Bq9Vy+fJlSpUqBcjPIUCTJk0AuHbtWpZ1V69eBaBkyZJyrv7jr7/+4vLlyzz//PO4ubkZrCu05+qJnzsTeo97fP6HH37IdvyEL7/8MtvBoZo3b640Go0KDg7Wt2WMn+Ds7KyioqKM+wby2dSpUxWgevfubTBw238V9/P04OCSD7py5Yry8PBQrq6uKjExUSkl5yo+Pl799NNPWb7mzZunANWuXTv1008/qdOnTyul5Hw97Hvrgw8+UIAaO3asvq24n6uYmBjl5uamSpUqpWJiYvTtd+/eVaVLl1bW1tbq6tWrSik5Vw/q06ePAtSuXbuyXV8Yz5UEoSe0dOlSNX36dDV9+nQVEBCgLCws9MvTp0832DYtLU21bNlSAWrQoEFq/vz5+hE1W7ZsmWVEzUOHDilHR0fl6empZsyYoebOnat//bx58wrybT6xOXPmKED5+fmpxYsXZxmB9KefftJvW5zPk1JKjR07VlWvXl299dZbas6cOeqbb75RY8aMUS4uLsrCwkItW7ZMv21xP1cP87BxhIr7+Ro7dqyqWbOmmjBhgvruu+/UZ599pp599lkFqKpVq6o7d+7oty3u50oppZYsWaIAVblyZTVz5kz16aefqqpVqypAffDBB/rt5Fzp3L59W9nY2KiqVas+dJvCeK4kCD2hjMGhHvb1X/fu3VPjx49XZcuWVdbW1qps2bJq/PjxDx2o6+jRo6pjx47K1dVV2dvbq0aNGqm1a9fm99syusGDBz/yPPn7+xtsX1zPk1JKbdu2TfXq1UsFBAQoBwcHZWNjo/z9/VW/fv3U/v37s2xfnM/VwzwsCClVvM/Xzz//rNq3b6/KlCmjbG1tlb29vapZs6Z6//33sx1csTifqwy//fabatWqlXJ0dNS/p1WrVmXZTs6VUp999pkC1GefffbI7QrbudIodX/kIiGEEEKIYkY6SwshhBCi2JIgJIQQQohiS4KQEEIIIYotCUJCCCGEKLYkCAkhhBCi2JIgJIQQQohiS4KQEEIIIYotCUJCCCGEKLYkCAkhhBCi2JIgJIQQxURQUBAajUb/9eOPPz7xPjt16mSwz8WLFz95oUIUIAlCQhQBD37Q5ORLPoxyZ8eOHWg0GqZMmWLqUgrE5MmTmTx5MjVr1jRozwhKO3bsyPKa+Ph42rdvj0ajoX379sTFxQHQr18/Jk+eTNeuXQuidCGMzsrUBQghHm/y5MlZ2r744gtiY2MZO3Ysbm5uBuvq1q1bMIWJIim3gS8yMpIOHTpw8OBB+vfvz6JFi7C2tgZ0QQhg8eLF/Pzzz8YuVYh8J0FIiCIguw+uxYsXExsby+uvv05AQECB1ySKh5CQENq3b8/58+cZN24cs2bNQqPRmLosIYxGbo0JYYb2799Pr169KFWqFDY2Nvj5+TF69GiuX7+eZduM2yGpqalMmzaNChUqYGdnR5UqVfjhhx/0233zzTfUrFkTe3t7fH19mTJlClqt1mBfISEhaDQahgwZwtmzZ+nWrRseHh44OjrSokUL/vjjj4fWvGrVKtq0aYO7uzt2dnZUq1aNGTNmkJycnGVbjUZDUFAQ169fZ+jQofj4+GBpaam/JXj+/HkmTJhAgwYNKFGiBLa2tvj7+zNy5EiuXr1qsK8hQ4bQpk0bAKZOnWpwizHjFtGUKVMeesvowff83/1qNBouX77MF198Qa1atbC3tycoKEi/TVRUFO+++y7VqlXD3t4eV1dXnnrqqWzPU3JyMp9//jn16tXD3d0dBwcH/Pz86Ny5M9u2bXvoeX0Sx48fp1mzZly4cIGZM2fy2WefSQgSZkeuCAlhZhYtWsTIkSOxs7OjS5cu+Pr6cuHCBebPn8/mzZvZt28fZcuWzfK6vn37sn//fjp06IC1tTXr1q1j1KhR2NjYEBwczMqVK+nUqRPt2rVj8+bNTJ06FXt7e955550s+7py5QpNmzalZs2ajB49moiICFavXs1zzz3HypUr6dOnj8H2w4cPZ+HChfj5+dGzZ09cXV3Zt28fEydO5K+//uKPP/7Q34rJcOfOHZo2bYqzszO9evVCKYW3tzcAGzZsYO7cubRp04ZmzZphY2PDyZMnWbBgAZs2beLQoUP4+voC0K1bNwCWLFlC69atDYKKMa60vfbaa/z777907NiRDh06YGlpCUBoaChBQUGEhITQqlUrnnvuOeLi4vjll1949tlnmTt3LqNGjdLvZ9CgQaxZs4aaNWsyaNAg7O3tuX79Ov/++y9bt27l6aeffuJaH7Rr1y66dOlCQkICS5YsYeDAgUbdvxCFhhJCFEn+/v4KUFeuXNG3nTt3TllbW6tKlSqp69evG2z/119/KQsLC9W1a1eD9tatWytANWjQQEVHR+vbL126pKytrZWrq6sKCAhQYWFh+nUxMTHKy8tLeXl5qdTUVH37lStXFKAA9dZbbxkc5+DBg8rKykq5ubmp2NhYffuiRYsUoHr16qUSExMNXjN58mQFqM8//9ygPeMYAwcONDh+hrCwMJWUlJSl/bffflMWFhZq9OjRBu3bt29XgJo8eXKW1zxYx/bt27Osy3jPgwcPNmgfPHiwAlTp0qXV5cuXs7yudevWSqPRqDVr1hi0R0dHqzp16ig7OzsVERGhlNKdb41Go+rXr6/S0tKy7CsyMjLburM75qN+7WesHzt2rLKzs1OOjo7q999/z9G+M/4dFy1alKPthSgs5NaYEGbku+++IzU1lS+++AIfHx+DdW3btqVLly5s3ryZu3fvZnntJ598YtDpunz58rRo0YLY2FgmTpxImTJl9OtcXV3p3LkzkZGRhIeHZ9mXq6srkyZNMmhr0KAB/fv3JyYmhp9++knf/uWXX2Jtbc0PP/yAnZ2dwWsmTpyIp6cnK1asyHIMGxsbZs2ahZVV1gvbZcqUwdbWNkv7c889R/Xq1R95i87Yxo8fT7ly5Qzajh07xs6dO+nVqxfPP/+8wTo3NzemTp1KUlIS69evB8DCwgKlFLa2tlhYZP217enpadSav/zyS5KSkpg7dy7PPvusUfctRGEjt8aEMCN79+4FdI+DHzhwIMv6W7duodVquXDhAvXr1zdY999lgNKlSz92XVhYGP7+/gbrAgMDcXZ2zvKaoKAglixZwpEjRxg8eDAJCQkcO3YMLy8vvvjii2zfk62tLWfPns3SHhAQoL8V9l9KKVasWMHixYs5duwY0dHRpKen69fb2Nhk+7r80Lhx4yxtGf9OMTEx2XaEv337NoD+fTs7O9O5c2c2b95MvXr16NmzJy1atKBx48Y4ODgYveb27duzdetWxo0bR+3ataldu7bRjyFEYSFBSAgzcufOHQA+/fTTR26XMQbMg1xdXbO0ZVxtedS61NTULOtKliyZ7XFLlSoFQGxsLADR0dEopbh9+zZTp059ZM0P21d2xo0bp78q1r59e8qUKYO9vT2ge9ouNDQ0V8d6EtnVmfHvtG3btkd2dH7w32n16tV88sknrFy5Un+1zc7Ojt69ezNr1ixKlChhtJonTJhAUFAQ7777Lm3atGHr1q00aNDAaPsXojCRICSEGckILLGxsbi4uJisjps3b2bbfuPGDSCzzow/69Wrx+HDh3N1jIc9vXTr1i2++uoratasyZ49e7JcmVq1alWujgPob0elpaVlWRcTE5PrOjPe95dffslrr72Woxrs7e2ZMmUKU6ZM4dq1a+zatYvFixezdOlSQkJC2LlzZ472k1MTJkzA3t6e119/naeeeorff/+dZs2aGfUYQhQG0kdICDPSpEkTAP755x+T1nH48GHu3buXpT3j8fN69eoB4OTkRI0aNTh16hRRUVFGOfbly5fRarU888wzWUJQWFgYly9fzvKajCe5Hrx99iB3d3cArl27lmVdcHBwrmt80n8nPz8/+vfvz9atW6lUqRK7du0y2vl70NixY/n++++Ji4vjmWeeYfv27UY/hhCmJkFICDMyZswYrK2teeONNzh//nyW9SkpKQUSkmJjY5k2bZpBW3BwMCtWrMDV1ZXu3bvr28eNG0dKSgrDhg3L9upKdHR0rq4WZTzy/u+//xoEm7i4OEaOHJntVZ2MzsbZBR3I7OezaNEig9dfu3Yty/vMiQYNGtCyZUs2bNjAwoULs93mxIkT3Lp1C9D1Gdq/f3+WbeLj47l37x6WlpbZdho3hpEjR7J48WKSkpLo2LEjW7ZsyZfjCGEqcmtMCDNStWpVFi5cyLBhw6hRowbPPvsslStXJjU1latXr/LPP/9QokSJbDsfG1OrVq2YP38++/fvp3nz5vpxhLRaLfPmzTO4bTds2DAOHTrEt99+S4UKFWjfvj1ly5YlKiqKK1eusGvXLoYOHcrcuXNzdOxSpUrRt29ffvzxR+rWrcszzzxDbGws27Ztw87Ojrp163L06FGD11SpUoUyZcrw448/Ym1tTdmyZdFoNAwcOBB/f38aNWpEUFAQO3bsoFGjRrRt25abN2+yefNm2rdv/9AA9SgrV66kbdu2DB8+nK+++orGjRvj5uZGWFgYx48f5+TJk+zduxdvb2/Cw8Np0qQJ1apVIzAwED8/P+7evcsvv/zCjRs3GDNmTL7eCh04cCB2dnb079+frl27smbNGplbTJgPEz++L4TIo+zGEcpw/PhxNXjwYFW2bFllY2Oj3N3dVY0aNdSoUaPUX3/9ZbDto8aWyRgLJ7tjZDe2zoNj6pw5c0Z16dJFubm5KXt7e9WsWTO1ZcuWh76fzZs3q44dO6oSJUooa2trVbJkSdWwYUP1v//9T505c8ZgW0C1bt36ofuKj49X7733nqpQoYKytbVVvr6+6uWXX1aRkZEPfb8HDhxQbdu2VS4uLkqj0WR5bzExMWrUqFGqRIkSysbGRtWoUUPNmzfvseMIZXfuMty9e1d98MEHKjAwUDk6Oio7OzsVEBCgOnTooObNm6fi4uKUUrqxhaZOnaratGmjSpcurWxsbFSpUqVU69at1cqVK5VWq33oMR6U03GEshsvSSmlNm3apGxtbZWVlZX68ccfDdbJOEKiqNIopZRJEpgQwuyEhIRQrlw5Bg8erJ/uQhQeQUFB7Ny5k/z4tb948WKGDh3KokWLskw3IkRhJn2EhBCimMmYS+3HH3984n116tQJjUbD0KFDjVCZEAVP+ggJIUQxMWTIEIO51GrWrPnE++zXr5/BGEN169Z94n0KUZAkCAkhRDGRH7es+vXrZ/R9ClGQpI+QEEIIIYot6SMkhBBCiGJLgpAQQgghii0JQkIIIYQotiQICSGEEKLYkiAkhBBCiGJLgpAQQgghii0JQkIIIYQotiQICSGEEKLYkiAkhBBCiGLr/6NkSI2uEUfLAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "F_md_nvt, fine_temperatures = get_ah_F(U_md_npt, temperatures=temperatures)\n", + "plt.title('NPT anharmonic free energy')\n", + "plt.xlabel('Temperatures [K]')\n", + "plt.ylabel('$F_{\\mathrm{ah}}$ [meV]')\n", + "plt.plot(fine_temperatures, F_md_nvt, label='MD')\n", + "plt.plot(fine_temperatures, F_mfcv_ps, label='mfc-lc-v-ps')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "98439c5b", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:02:12.900513Z", + "start_time": "2022-12-01T14:02:12.753701Z" + } + }, + "outputs": [], + "source": [ + "# load npt runs to get npt volume\n", + "md_jobs = []\n", + "for i in range(len(temperatures)):\n", + " temp_jobs = []\n", + " for j in range(samples):\n", + " temp_jobs.append(pr_md_npt.inspect('npt_temp_' + str(i) + '_sample_' + str(j)))\n", + " md_jobs.append(temp_jobs)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "80e028c4", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:02:12.944675Z", + "start_time": "2022-12-01T14:02:12.900684Z" + } + }, + "outputs": [], + "source": [ + "volumes = np.array([np.mean([md_jobs[i][j]['output/generic/volume'][200:] for j in range(samples)])\n", + " for i, _ in enumerate(temperatures)])\n", + "lattice_a = np.cbrt(volumes)/4" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "a586d484", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T14:02:13.112232Z", + "start_time": "2022-12-01T14:02:12.944979Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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ZXjcfocKOyIqPYP3Y2Pw9CyVs4mKiIjkUyqs8om9x0M5vmwSj95cQmNTdModKuy4Xr47vu3/h7eXX84mKSIiks+psMoHdm1ewUPrhlDjtoIqAVsulX2SEo+/gW8h/5xNUEREpIBQYZUPVKvdhFvrbQBzQXU2oBulO71JycIBOZuYiIhIAaPCKh+wdy1EaLXnuHL9FKW7vEWAT2BOpyQiIlIgqbDKJ8o9MTanUxARESnwbHI6AREREZH8QoWViIiIiJWosBIRERGxEhVWIiIiIlaiwkpERETESlRYiYiIiFiJCisRERERK1FhJSIiImIlKqxERERErESFlYiIiIiVqLASERERsRIVViIiIiJWosJKRERExErscjqBgubWrVsAHD58OIczERERkYxK/t5O/h6/ExVW2Sw0NBSA3r1752wiIiIict9CQ0Np0qTJHfebDMMwsjGfAu/q1ausWbOGgIAAnJ2drdbv4cOH6d27N9999x2VKlWyWr/5la5XxulaZZyuVcbpWmWcrlXGZeW1unXrFqGhobRt2xYfH587ttMdq2zm4+NDr169sqz/SpUqUbt27SzrP7/R9co4XauM07XKOF2rjNO1yrisulZ3u1OVTA+vi4iIiFiJCisRERERK1FhJSIiImIlKqxERERErESFVT5RvHhxgoKCKF68eE6nkifoemWcrlXG6VplnK5VxulaZVxuuFaabkFERETESnTHSkRERMRKVFiJiIiIWIkKKxERERErUWGVy7z//vv06NGDcuXKYWNjg53d3SfHj46OZsyYMQQEBODo6EhAQABjxowhOjo63fb79u2jY8eOFCpUCFdXVxo2bMjPP/+cFR8lSx07doygoCAaN26Mr68vbm5uVKtWjddff53r16+naV9Qr1Oyq1evMnDgQGrUqIG3tzdOTk6UKVOGZ555hr1796ZpX9Cv1+2ioqIIDAzEZDIxePDgNPsL+rUymUx3/ImMjEzVtqBfK4AbN27w5ptvUqlSJZydnSlcuDANGjTgu+++S9WuIF+rcePG3fXPlclk4ty5c5b2ue5aGZKrAIaXl5fRqlUro1ixYoatre0d2yYkJBgtWrQwAKNPnz7G119/bbzwwguGra2t0bJlSyMhISFV+z179hhubm6Gt7e3MWHCBGP69OlG06ZNDcCYOXNmVn80q3rttdcMV1dX4+mnnzY+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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.title('Lattice expansion')\n", + "plt.xlabel('Temperatures [K]')\n", + "plt.ylabel('$a [\\mathrm{\\AA}]$')\n", + "plt.plot(temperatures, lattice_a, label='MD')\n", + "plt.plot(temperatures, global_strains*a_0*np.sqrt(2), label='mfc-lc-v-ps')\n", + "plt.legend()\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8 (XPython)", + "language": "python", + "name": "xpython" + }, + "language_info": { + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "version": "3.8.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyiron_contrib/atomistics/mean_field/morse_al_mf_nvt.ipynb b/pyiron_contrib/atomistics/mean_field/morse_al_mf_nvt.ipynb new file mode 100644 index 000000000..a35849cd3 --- /dev/null +++ b/pyiron_contrib/atomistics/mean_field/morse_al_mf_nvt.ipynb @@ -0,0 +1,810 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "04ace7b5", + "metadata": {}, + "source": [ + "# Bond lattice mean-field model for a Morse potential - NVT" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "08515601", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T12:39:29.787937Z", + "start_time": "2022-12-01T12:39:18.101470Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/cmmc/u/system/SLES12/soft/pyiron/dev/anaconda3/lib/python3.8/site-packages/pkg_resources/__init__.py:123: PkgResourcesDeprecationWarning: NOT-A-GIT-REPOSITORY is an invalid version and will not be supported in a future release\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "7aef85872b764f33b9afa27055579fa6", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from pyiron_atomistics import Project\n", + "\n", + "from scipy.interpolate import UnivariateSpline, CubicSpline\n", + "from scipy.optimize import root_scalar, root\n", + "from scipy.integrate import cumtrapz\n", + "\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "from matplotlib import gridspec\n", + "\n", + "from scipy.constants import physical_constants\n", + "KB = physical_constants['Boltzmann constant in eV/K'][0]\n", + "\n", + "from pyiron_contrib.atomistics.mean_field.core.bond_analysis import StaticBondAnalysis, MDBondAnalysis\n", + "\n", + "import seaborn as sns\n", + "sns.set_context('paper', font_scale=1.5, rc={\"lines.linewidth\": 2})" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c9c25b36", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T12:39:29.793519Z", + "start_time": "2022-12-01T12:39:29.788229Z" + } + }, + "outputs": [], + "source": [ + "alpha = 1.5\n", + "pr = Project('morse_al/mfm')\n", + "pr_md_nvt = Project('morse_al/md_runs_nvt/alpha_' + str(alpha).replace('.', '_'))" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d1e84e27", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T12:39:29.800805Z", + "start_time": "2022-12-01T12:39:29.793725Z" + } + }, + "outputs": [], + "source": [ + "# potential functions\n", + "D = 0.1\n", + "a_0 = 2.856\n", + "kappa = 0.\n", + "\n", + "# for lammps\n", + "def md_morse(D=D, alpha=alpha, r_0=a_0, b=1):\n", + " config = 'atom_style bond\\nbond_style morse\\n'\n", + " for i in range(b):\n", + " vals = (i+1, D, alpha, a_0)\n", + " config += 'bond_coeff %d %.7f %.7f %.7f\\n'%(vals)\n", + " return pd.DataFrame({'Name': ['Morse'],\n", + " 'Filename': [[]], \n", + " 'Model' : ['Morse'], \n", + " 'Species' : [['Al']], \n", + " 'Config' : [[config]]})\n", + "\n", + "# longitudinal_potential\n", + "def morse(r, D=D, alpha=alpha, a_0=a_0):\n", + " return D*(1.0+np.exp(-2.0*alpha*(r-a_0)) - 2.0*np.exp(-alpha*(r-a_0)))\n", + "\n", + "def dmorse(r, D=D, alpha=alpha, a_0=a_0):\n", + " return -2.0*alpha*D*(np.exp(-2.0*alpha*(r-a_0)) - np.exp(-alpha*(r-a_0)))\n", + "\n", + "# harmonic potential\n", + "def harm(r, kappa=kappa):\n", + " return D*alpha*alpha*kappa*(r**2)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "f1997bca", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T12:39:29.807240Z", + "start_time": "2022-12-01T12:39:29.800985Z" + } + }, + "outputs": [], + "source": [ + "# structure\n", + "element = 'Al'\n", + "potential = md_morse()\n", + "supercell = 4\n", + "n_atoms = 4*supercell**3\n", + "temperatures = np.linspace(100, 900, 9)\n", + "samples = 5" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ddf9c025", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T12:39:31.207687Z", + "start_time": "2022-12-01T12:39:29.807441Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The job zero was saved and received the ID: 18414706\n" + ] + } + ], + "source": [ + "# minimize structure\n", + "zero = pr.create.job.Lammps('zero', delete_existing_job=True)\n", + "zero.structure = pr.create.structure.bulk(element, cubic=True).repeat(supercell)\n", + "zero.potential = potential\n", + "zero.calc_minimize(pressure=0.0001)\n", + "zero.run()\n", + "structure = zero.get_structure()\n", + "a_0 = (structure.cell/supercell/np.sqrt(2))[0][0]\n", + "U_0 = zero.output.energy_pot[-1]/n_atoms" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "8d6b917f", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T12:39:31.841612Z", + "start_time": "2022-12-01T12:39:31.209275Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The job stat_ba was saved and received the ID: 18414707\n" + ] + } + ], + "source": [ + "# analyze bonds and get rotations and displacement matrix\n", + "stat_ba = pr.create_job(StaticBondAnalysis, 'stat_ba', delete_existing_job=True)\n", + "stat_ba.input.structure = structure.copy()\n", + "stat_ba.input.n_shells = 1\n", + "stat_ba.run()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "6749c0f7", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T12:39:31.842974Z", + "start_time": "2022-12-01T12:39:31.841807Z" + } + }, + "outputs": [], + "source": [ + "# normalized displacement vectors along which to displace our atom\n", + "disp_matrix = np.array(stat_ba.output.per_shell_transformation_matrices[0][0])\n", + "# these are also the normalized long, t1 and t2 vectors\n", + "long_hat, t1_hat, t2_hat = disp_matrix\n", + "# rotations\n", + "rotations = stat_ba.output.per_shell_0K_rotations[0]\n", + "# a_xyz\n", + "a_xyz = long_hat*a_0" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "e3de985e", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T12:39:39.408764Z", + "start_time": "2022-12-01T12:39:31.843144Z" + } + }, + "outputs": [], + "source": [ + "# load the nvt jobs from the md runs\n", + "md_jobs_nvt = []\n", + "for i in range(len(temperatures)):\n", + " temp_jobs = []\n", + " for j in range(samples):\n", + " temp_jobs.append(pr_md_nvt.load('nve_temp_' + str(i) + '_sample_' + str(j)))\n", + " md_jobs_nvt.append(temp_jobs)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "32ad89bd", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T12:39:41.585289Z", + "start_time": "2022-12-01T12:39:39.409009Z" + } + }, + "outputs": [], + "source": [ + "# extract mean anharmonic internal energy from the md run\n", + "def get_md_ah_U(temp_jobs, temperature, snapshots=200):\n", + " ah_U_pa = []\n", + " for job in temp_jobs:\n", + " if job.status == 'finished':\n", + " U_pa = np.mean(job['output/generic/energy_pot'][snapshots:])/n_atoms - U_0\n", + " T_virial = np.mean(job['output/generic/temperature'][snapshots:])\n", + " ah_U_pa.append(U_pa - 1.5*KB*T_virial)\n", + " return np.mean(ah_U_pa)*1000, np.std(ah_U_pa)/np.sqrt(len(temp_jobs))*1000 # in meV\n", + "\n", + "U_md_nvt = []\n", + "U_md_nvt_err = []\n", + "for (temp_jobs, temp) in zip(md_jobs_nvt, temperatures):\n", + " ah_U_pa, ah_U_pa_err = get_md_ah_U(temp_jobs, temp)\n", + " U_md_nvt.append(ah_U_pa)\n", + " U_md_nvt_err.append(ah_U_pa_err)\n", + "U_md_nvt = np.array(U_md_nvt)\n", + "U_md_nvt_err = np.array(U_md_nvt_err)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "d2509a66", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T12:40:13.530109Z", + "start_time": "2022-12-01T12:39:41.585524Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The job md_ba_100_0 was saved and received the ID: 18414708\n", + "The job md_ba_200_0 was saved and received the ID: 18414709\n", + "The job md_ba_300_0 was saved and received the ID: 18414710\n", + "The job md_ba_400_0 was saved and received the ID: 18414711\n", + "The job md_ba_500_0 was saved and received the ID: 18414712\n", + "The job md_ba_600_0 was saved and received the ID: 18414713\n", + "The job md_ba_700_0 was saved and received the ID: 18414714\n", + "The job md_ba_800_0 was saved and received the ID: 18414715\n", + "The job md_ba_900_0 was saved and received the ID: 18414716\n" + ] + } + ], + "source": [ + "# analyze MD bonds for the highest temperature\n", + "md_ba_jobs = []\n", + "for (temp, temp_jobs) in zip(temperatures, md_jobs_nvt):\n", + " md_job = temp_jobs[0]\n", + " md_ba = pr.create_job(MDBondAnalysis, 'md_ba_nvt' + str(temp).replace('.','_'), delete_existing_job=True)\n", + " md_ba.input.structure = structure.copy()\n", + " md_ba.input.n_shells = 1\n", + " md_ba.input.md_job = md_job.copy()\n", + " md_ba.input.thermalize_snapshots = 200\n", + " md_ba.run()\n", + " md_ba_jobs.append(md_ba)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "7526d922", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T12:40:13.532353Z", + "start_time": "2022-12-01T12:40:13.530326Z" + } + }, + "outputs": [], + "source": [ + "# bond arrangement functions\n", + "\n", + "# for 1d\n", + "def get_b_array(long_samples, t1_samples, t2_samples):\n", + " b_array = np.outer(long_samples, long_hat) + np.outer(t1_samples, t1_hat) + np.outer(t2_samples, t2_hat)\n", + " return b_array\n", + "\n", + "# for 3d\n", + "def get_sample_mesh(long_samples, t1_samples, t2_samples):\n", + " long_mesh, t1_mesh, t2_mesh = np.meshgrid(long_samples, t1_samples, t2_samples, indexing='ij')\n", + " return long_mesh, t1_mesh, t2_mesh\n", + "\n", + "def get_b_grid(long_mesh, t1_mesh, t2_mesh):\n", + " b_grid = np.tensordot(long_mesh, long_hat, axes=0) + np.tensordot(t1_mesh, t1_hat, axes=0) + \\\n", + " np.tensordot(t2_mesh, t2_hat, axes=0)\n", + " return b_grid" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "bf701729", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T12:40:21.236963Z", + "start_time": "2022-12-01T12:40:13.532511Z" + } + }, + "outputs": [], + "source": [ + "# create bond vectors from the MD bond distribution (so a seperate one for each temperature)\n", + "n_bins = 51\n", + "long_meshs, t1_meshs, t2_meshs = [], [], []\n", + "for c in range(len(temperatures)):\n", + " md_rho, md_bins = md_ba_jobs[c].get_3d_histogram_long_t1_t2(n_bins=n_bins, shell=0)\n", + " long_meshs.append(md_bins[0])\n", + " t1_meshs.append(md_bins[1])\n", + " t2_meshs.append(md_bins[2])\n", + " \n", + "# since our rotations are done in the xyz coordinate system, we convert them to xyz\n", + "b_grids = []\n", + "for c in range(len(temperatures)):\n", + " b_grids.append(get_b_grid(long_meshs[c], t1_meshs[c], t2_meshs[c]))" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "07c0556b", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T12:40:21.237941Z", + "start_time": "2022-12-01T12:40:21.237206Z" + } + }, + "outputs": [], + "source": [ + "# # create custom meshs\n", + "# n_bins = 51\n", + "# long_samples = np.linspace(1.5, 4.5, n_bins)\n", + "# t1_samples = np.linspace(-1.5, 1.5, n_bins)\n", + "# t2_samples = t1_samples.copy()\n", + "# long_mesh, t1_mesh, t2_mesh = get_sample_mesh(long_samples, t1_samples, t2_samples)\n", + "# b_grids = [get_b_grid(long_mesh, t1_mesh, t2_mesh)]*len(temperatures)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "005004e3", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T12:40:21.254880Z", + "start_time": "2022-12-01T12:40:21.238194Z" + } + }, + "outputs": [], + "source": [ + "# single bonding potential\n", + "def V_1(b_xyz):\n", + " r = np.linalg.norm(b_xyz, axis=-1)\n", + " return morse(r)\n", + "\n", + "# single bonding potential gradient\n", + "def dV_1(b_xyz):\n", + " V = V_1(b_xyz=b_xyz)\n", + " long_samples = np.dot(b_xyz, long_hat)[:, 0, 0]\n", + " t1_samples = np.dot(b_xyz, t1_hat)[0, :, 0]\n", + " t2_samples = np.dot(b_xyz, t2_hat)[0, 0, :]\n", + " return np.gradient(V, long_samples, t1_samples, t2_samples, edge_order=2)\n", + "\n", + "# each mean field component\n", + "def V_mf_component(b_xyz, rotation, a_s=1., a_ex=1.):\n", + " \"\"\"\n", + " here, a_s=the 'global' strain and a_ex=the 'pseudo' strain, which is equal to the 'global' strain at P=0 for \n", + " a given temperature. Once a_ex is established for a temperature, it remains the same for that temperature for \n", + " all new 'global' strains that we would like our model to predict free energies for.\n", + " \"\"\"\n", + " return V_1((b_xyz-a_xyz*a_s*a_ex)@rotation.T+a_xyz*a_s*a_ex)\n", + "\n", + "# mean field potential\n", + "def V_mf(b_xyz, a_s=1., a_ex=1.):\n", + " vmf = V_1(b_xyz)/2\n", + " for rot in rotations[1:]:\n", + " vmf += V_mf_component(b_xyz=b_xyz, rotation=rot, a_s=a_s, a_ex=a_ex)/2\n", + " return vmf\n", + "\n", + "# mean field correlated potential\n", + "def V_mfc(b_xyz, a_s=1., a_ex=1.):\n", + " vmfc = V_mf_component(b_xyz=b_xyz, rotation=rotations[0], a_s=a_s, a_ex=a_ex)\n", + " for i, rot in enumerate(rotations):\n", + " if i not in [0, 1]:\n", + " vmfc += V_mf_component(b_xyz=b_xyz, rotation=rot, a_s=a_s, a_ex=a_ex)/2\n", + " return vmfc\n", + "\n", + "# # linear correction function 1-d\n", + "# def find_linear_correction(temperature=100., a_s=1., a_ex=1., nstep=10000, minr=0.1, maxr=5.):\n", + "# r = np.linspace(minr, maxr, nstep, endpoint=True)\n", + "# r_array = get_b_array(r, np.zeros(nstep), np.zeros(nstep))\n", + "# V = V_mfc(b_xyz=r_array, a_s=a_s, a_ex=a_ex)\n", + "# V -= V.min()\n", + "# sel = V/temperature/KB<200.0 \n", + "# r, V = r[sel], V[sel]\n", + "# aa = a_s*a_0\n", + "# def f(m):\n", + "# rho = brho * np.exp(-m*(r-aa)/temperature/KB)\n", + "# res = np.abs(np.log((rho*r).sum()/rho.sum()/aa)).sum()\n", + "# return res\n", + "# brho = np.exp(-(V-V.min())/temperature/KB)\n", + "# solver = root_scalar(f, x0=0.0, x1=0.01, rtol=1e-8)\n", + "# return solver.root\n", + "\n", + "# linear correction function\n", + "def find_linear_correction(b_xyz, temperature=100., a_s=1., a_ex=1.):\n", + " \"\"\"\n", + " since the 1-d linear correction was not correctly setting =a (bug in the code?) I use the 3d bond vectors\n", + " to find the linear correction. This works.\n", + " \"\"\"\n", + " V = V_mfc(b_xyz=b_xyz, a_s=a_s, a_ex=a_ex)\n", + " V -= V.min()\n", + " long_mesh = np.dot(b_xyz, long_hat)\n", + " sel = V/temperature/KB<200.0 \n", + " long_mesh, V = long_mesh[sel], V[sel]\n", + " aa = a_s*a_0\n", + " def f(m):\n", + " rho = brho*np.exp(-m*(long_mesh-aa)/temperature/KB)\n", + " res = np.abs(np.log((rho*long_mesh).sum()/rho.sum()/aa)).sum()\n", + " return res\n", + " brho = np.exp(-(V-V.min())/temperature/KB)\n", + " solver = root_scalar(f, x0=0.0, x1=0.01, rtol=1e-8)\n", + " return solver.root\n", + "\n", + "# mean field correlated potential with linear correction term\n", + "def V_mfc_lc(b_xyz, temperature=100., a_s=1., a_ex=1.):\n", + " lm = find_linear_correction(b_xyz=b_xyz, temperature=temperature, a_s=a_s, a_ex=a_ex)\n", + " # NOTE here that the additional local strain is NOT considered here, as this will make the model predict\n", + " # the correction term at that strain, thus violating =a.\n", + " lc = lm*(np.dot(b_xyz, long_hat)-a_0*a_s)\n", + " vmfclc = V_mfc(b_xyz=b_xyz, a_s=a_s, a_ex=a_ex)+lc\n", + " return vmfclc\n", + "\n", + "# helper method to find virial quantities\n", + "def virial_helper(b_xyz, a_s=1., a_ex=1., pressure=False):\n", + " dV = np.array(dV_1(b_xyz))\n", + " if not pressure:\n", + " db_dV = (np.dot(b_xyz, long_hat)-a_0*a_s)*dV[0] + np.dot(b_xyz, t1_hat)*dV[1] + np.dot(b_xyz, t2_hat)*dV[2]\n", + " else:\n", + " db_dV = a_0*a_s*dV[0] # + np.dot(b_xyz, t1_hat)*dV[1] + np.dot(b_xyz, t2_hat)*dV[2]\n", + " return db_dV\n", + "\n", + "# bonding density function\n", + "def get_rho(v, temperature=100.):\n", + " rho = np.exp(-(v-v.min())/KB/ temperature)\n", + " return rho/rho.sum()\n", + "\n", + "# virial temperature\n", + "def find_virial_T(b_xyz, temperature=100., a_s=1., a_ex=1.):\n", + " db_dV = virial_helper(b_xyz, a_s=a_s, a_ex=a_ex)\n", + " vmfclc = V_mfc_lc(b_xyz=b_xyz, temperature=temperature, a_s=a_s, a_ex=a_ex)\n", + " rho = get_rho(v=vmfclc, temperature=temperature)\n", + " return 2./KB*(rho*db_dV).sum()\n", + "\n", + "# virial pressure\n", + "def find_virial_P(b_xyz, temperature=100., a_s=1., a_ex=1.):\n", + " db_dV = virial_helper(b_xyz, a_s=a_s, a_ex=a_ex, pressure=True)\n", + " vmfclc = V_mfc_lc(b_xyz=b_xyz, temperature=temperature, a_s=a_s, a_ex=a_ex)\n", + " rho = get_rho(v=vmfclc, temperature=temperature)\n", + " N_by_V = n_atoms/((a_0*a_s*np.sqrt(2)*supercell)**3)\n", + " return N_by_V*(KB*temperature+4.*((rho*db_dV).sum()))\n", + "\n", + "# 2 virials, 1 method\n", + "def find_virial(b_xyz, temperature=100., a_s=1., a_ex=1.):\n", + " \"\"\"\n", + " Find both the virial temperature and pressure in the same method.\n", + " \"\"\"\n", + " vmfclc = V_mfc_lc(b_xyz=b_xyz, temperature=temperature, a_s=a_s, a_ex=a_ex)\n", + " rho = get_rho(v=vmfclc, temperature=temperature)\n", + " db_dV_T = virial_helper(b_xyz=b_xyz, a_s=a_s, a_ex=a_ex)\n", + " virial_T = 2./KB*(rho*db_dV_T).sum()\n", + " db_dV_P = virial_helper(b_xyz, a_s=a_s, a_ex=a_ex, pressure=True)\n", + " N_by_V = n_atoms/((a_0*a_s*np.sqrt(2)*supercell)**3)\n", + " virial_P = N_by_V*(KB*temperature+4.*((rho*db_dV_P).sum()))\n", + " return virial_T, virial_P\n", + "\n", + "# effective temperature\n", + "def find_eff_T(b_xyz, temperature=100., a_s=1., a_ex=1.):\n", + " print(\"Initial temperature: {}\".format(temperature))\n", + " def dvirialE(eff_temperature):\n", + " res = find_virial_T(b_xyz=b_xyz, temperature=eff_temperature, a_s=a_s, a_ex=a_ex)\n", + " return res/(temperature+1e-20)-1.\n", + " solver = root_scalar(dvirialE, x0=temperature, x1=temperature+10, rtol=1e-8)\n", + " print(\"Effective temperature: {}\".format(solver.root))\n", + " return solver.root\n", + " \n", + "# effective temperature + strain\n", + "def find_eff_strain_and_T(b_xyz, temperature=100., pressure=1e-10, a_s=1., a_ex=1.):\n", + " \"\"\"\n", + " Optimize both the effective temperature and strain corresponding to a given pressure. Presently, I use this\n", + " method to set my pressure to 0 to obtain a_ex.\n", + " \"\"\"\n", + " def dvirialPT(arg):\n", + " res_T, res_P = find_virial(b_xyz=b_xyz, temperature=arg[0], a_s=arg[1], a_ex=arg[1])\n", + " return [res_T/(temperature+1e-20)-1., res_P/(pressure+1e-20)-1.]\n", + " solver = root(dvirialPT, x0=(temperature, a_s), tol=1e-8)\n", + " print('Effective temperature: {}\\nGlobal strain: {}'.format(solver.x[0], solver.x[1]))\n", + " return solver.x\n", + "\n", + "# strain_at_pressure\n", + "def find_strain_at_pressure(b_xyz, temperature=100., pressure=1e-20, a_ex=1., a_s=1.):\n", + " \"\"\"\n", + " seperate method to find the strain at an input pressure.\n", + " \"\"\"\n", + " def f(a_s):\n", + " res = find_virial_P(b_xyz=b_xyz, temperature=temperature, a_s=a_s, a_ex=a_ex)\n", + " return res/(pressure+1e-20)-1.\n", + " solver = root_scalar(f, x0=a_ex*0.99, x1=a_ex*1.01, rtol=1e-8)\n", + " return solver.root\n", + "\n", + "# get ah U's using this function...\n", + "def get_ah_U(rhos, temperatures=temperatures):\n", + " return np.array([(6*(((V_1(b_grids[i]) - V_1(a_xyz))*rho).sum()) - 1.5*KB*t)*1000 \n", + " for i, (rho, t) in enumerate(zip(rhos, temperatures))])\n", + "\n", + "# get ah F's using this function...\n", + "def get_ah_F(ah_Us, temperatures=temperatures, n_fine=10000):\n", + " fine_temperatures = np.linspace(temperatures[0], temperatures[-1], n_fine, endpoint=True)\n", + " ah_U_eqn = UnivariateSpline(x=temperatures, y=ah_Us, k=3, s=0)\n", + " return -cumtrapz(ah_U_eqn(fine_temperatures), 1/fine_temperatures)*fine_temperatures[1:], fine_temperatures[1:]" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "358fc4a9", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T12:40:32.681284Z", + "start_time": "2022-12-01T12:40:21.255102Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Initial temperature: 100.0\n", + "Effective temperature: 102.27073193969343\n", + "Initial temperature: 200.0\n", + "Effective temperature: 208.30874684550085\n", + "Initial temperature: 300.0\n", + "Effective temperature: 317.3666072552088\n", + "Initial temperature: 400.0\n", + "Effective temperature: 428.1827095112276\n", + "Initial temperature: 500.0\n", + "Effective temperature: 540.7044354412858\n", + "Initial temperature: 600.0\n", + "Effective temperature: 654.4027427164102\n", + "Initial temperature: 700.0\n", + "Effective temperature: 766.6114678679114\n", + "Initial temperature: 800.0\n", + "Effective temperature: 879.6841310785211\n", + "Initial temperature: 900.0\n", + "Effective temperature: 992.2656375806246\n" + ] + } + ], + "source": [ + "# collect effective temperatures using the old approach\n", + "eff_temperatures_old = np.array([find_eff_T(b_grid, temp) for temp, b_grid in zip(temperatures, b_grids)])" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "73c19a10", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T12:41:06.146230Z", + "start_time": "2022-12-01T12:40:32.681498Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Effective temperature: 99.84299423194422\n", + "Global strain: 1.0060857914733892\n", + "Effective temperature: 199.1268613415269\n", + "Global strain: 1.0126535077823045\n", + "Effective temperature: 297.72108879440196\n", + "Global strain: 1.0197490143622303\n", + "Effective temperature: 394.3199506973771\n", + "Global strain: 1.0276349109964757\n", + "Effective temperature: 489.90350390534894\n", + "Global strain: 1.0361942420182997\n", + "Effective temperature: 584.6048844327706\n", + "Global strain: 1.0455876039896872\n", + "Effective temperature: 671.5812667636701\n", + "Global strain: 1.0571505314103442\n", + "Effective temperature: 761.2374212571572\n", + "Global strain: 1.0701544460546282\n", + "Effective temperature: 856.5602020395352\n", + "Global strain: 1.08696032519274\n" + ] + } + ], + "source": [ + "# collect effective temperatures and pseudo strains for each temperature using the new approach\n", + "eff_parameters = np.array([find_eff_strain_and_T(b_xyz, t) for b_xyz, t in zip(b_grids, temperatures)]).T\n", + "eff_temperatures_new, pseudo_strains = eff_parameters" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "682ce597-dbc6-427d-8a5f-8f45bc31532e", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T12:41:10.13936Z", + "start_time": "2022-12-01T12:41:06.146462Z" + } + }, + "outputs": [], + "source": [ + "# ah_Us and ah_Fs for different methods\n", + "\n", + "# vmf = []\n", + "# mf_rhos = []\n", + "# for i, temp in enumerate(temperatures):\n", + "# v = V_mfc(b_grids[i])\n", + "# vmf.append(v)\n", + "# mf_rhos.append(get_rho(v, temp))\n", + "# U_mf = get_ah_U(mf_rhos, temperatures=temperatures)\n", + "# F_mf, fine_temperatures = get_ah_F(U_mf, temperatures=temperatures)\n", + "\n", + "# vmfc = []\n", + "# mfc_rhos = []\n", + "# for i, temp in enumerate(temperatures):\n", + "# v = V_mfc_lc(b_grids[i], temperature=temp)\n", + "# vmfc.append(v)\n", + "# mfc_rhos.append(get_rho(v, temp))\n", + "# U_mfc = get_ah_U(mfc_rhos, temperatures=temperatures)\n", + "# F_mfc, fine_temperatures = get_ah_F(U_mfc, temperatures=temperatures)\n", + "\n", + "vmfcv = []\n", + "mfcv_rhos = []\n", + "for i, (eff_temp) in enumerate(eff_temperatures_old):\n", + " v = V_mfc_lc(b_grids[i], temperature=eff_temp)\n", + " vmfcv.append(v)\n", + " mfcv_rhos.append(get_rho(v, eff_temp))\n", + "U_mfcv = get_ah_U(mfcv_rhos, temperatures=temperatures)\n", + "F_mfcv, fine_temperatures = get_ah_F(U_mfcv, temperatures=temperatures)\n", + "\n", + "# fixed volume = no global strain, only pseudo strain\n", + "vmfcv_ps = []\n", + "mfcv_ps_rhos = []\n", + "for i, (eff_temp, ex) in enumerate(zip(eff_temperatures_new, pseudo_strains)):\n", + " v = V_mfc_lc(b_grids[i], a_s=1., a_ex=ex, temperature=eff_temp)\n", + " vmfcv_ps.append(v)\n", + " mfcv_ps_rhos.append(get_rho(v, eff_temp))\n", + "U_mfcv_ps = get_ah_U(mfcv_ps_rhos, temperatures=temperatures)\n", + "F_mfcv_ps, fine_temperatures = get_ah_F(U_mfcv_ps, temperatures=temperatures)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "4736e364-ec0a-4750-bef4-fab3fcd555f3", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T12:41:10.317932Z", + "start_time": "2022-12-01T12:41:10.14210Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkYAAAHTCAYAAADCntcBAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8o6BhiAAAACXBIWXMAAA9hAAAPYQGoP6dpAACP0klEQVR4nOzdd3gUVdvA4d/sZtN7DxASeu9SpAgoyGtBFBEUQVCaBUVRFN9PBUTR1wZ2EVCqgtiwC0pRUbpgoZeEFkghve5mz/fHJJssKSQhyaY893XNRfbMmZnnJEv2yTlnzmhKKYUQQgghhMDg6ACEEEIIIWoKSYyEEEIIIfJIYiSEEEIIkUcSIyGEEEKIPJIYCSGEEELkkcRICCGEECKPJEZCCCGEEHkkMRJCCCGEyCOJkRBCCCFEHkmMhKglxo8fj6ZpREVFOTqUWiUyMpLIyMgqObemaQwYMKBKzl3bLV26FE3TWLp0qaNDEaJcJDESNYamaWiaRkREBFlZWcXWiYyMRNM0LBYLAKNHj0bTNN59991Lnn/gwIFomsZbb71lu1ZZNyGq0oABA+R9JkQN4eToAIS42MmTJ1mwYAEzZ868ZN3Jkyfz8ccfs2jRIu67774S6x07dowtW7bQsGFDrrzySmbNmmW3PyoqimXLlhEREcH48eMvtwmiBvn555+r7NwHDhzA3d29ys4vhKh+khiJGsXPzw9N03jhhReYOHEigYGBpdYfMGAALVu25M8//2TPnj107dq12HqLFy9GKcU999xDt27d6Natm93+zZs3s2zZMiIjI5k9e3ZlNUfUAM2aNauyc7du3brKzi2EcAwZShM1iru7O08//TQpKSnMmTOnTMdMmjQJ0JOf4lgsFpYuXYrBYGDChAmVFmthu3fvZtq0aXTq1Al/f39cXV1p0aIF06dP58KFC0XqF55/sWnTJgYMGICXlxfe3t5cf/31/Pvvv6Veb+HChXTo0AFXV1dCQkKYNGkSSUlJRept2rSJyZMn07ZtW7y9vXFzc6Ndu3bMmjWLzMzMIvVnz56Npmls3ryZ5cuX0717dzw8PGxzdArv//jjj+nWrRvu7u40aNCA6dOnk52dDcCGDRvo378/Xl5e+Pn5cddddxX7fQDYtWsXw4cPJzg4GBcXFyIiIrjvvvs4e/ZskbqF51mV9XtQ2hyjNWvWcM0119h+ZpGRkdxxxx3s2rWr+G/8RYqbY1T4e/Tpp5/So0cP3N3d8ff3Z9SoUZw+fdpWNyoqCk3T2LJli+18+dvF5z19+jRTp06ladOmuLi4EBAQwE033cTOnTuLxFWen+OlYsxX3vd4RVgsFt555x169eqFt7c37u7udOnShbfeegur1WpXN/97N378eKKiorj99tsJDAzE1dWVbt268dVXX5V4nY8//piBAwfi5+eHq6srbdq04bnnnrO9fwvL/1mcPXuWu+++m7CwMIxGo93cqVWrVtG1a1fc3NwIDg5m7NixnD17tsgQ6Q8//ICmadxzzz3FxpWdnU1gYCCBgYHFxiKqiRKihgBUw4YNVU5OjmrWrJkymUzq0KFDdnUiIiIUoMxms60sNjZWOTs7Kx8fH5WRkVHkvF988YUC1H/+858Sr71p0yYFqP79+1co9ilTpqjg4GB12223qenTp6tp06apPn36KEC1atVKpaSk2NX/8MMPFaBuvfVW5eTkpIYOHaoee+wxdf311ytABQYGqtjYWLtjxo0bpwB12223KW9vb3XnnXeq6dOnqy5duihAXXXVVUXiGjJkiIqIiFB33HGHeuyxx9QDDzygOnfurADVr18/u++jUkrNmjVLAeqGG25QLi4uasSIEerxxx9XkydPtts/fPhw5ebmpu644w41ffp01b59ewWo8ePHqzVr1ihnZ2d1yy23qMcee0z17t27xO//l19+qUwmk3J2dlajR49WM2fOVIMGDVKACgsLU8eOHbvs70FERISKiIiwK7NarbZzBQYGqgkTJqiZM2eqO++8UzVo0EDNmjWrxJ91YcW9Z/K/R7fddptycXFRt912m3rsscdUv379FKBatmypsrKylFJKJSYmqlmzZtne17NmzbJtH374oe2cu3fvVgEBAUrTNPWf//xHPfroo2rcuHHKx8dHOTs7q2+//bZCP8eyxJivou/xwu0oTU5OjhoyZIgCVOvWrdWUKVPUtGnTVMeOHRWg7rzzTrv6J06cUIAaMGCACgoKUj179lQPP/ywuuuuu5SLi4vSNE399NNPRa5zzz33KECFh4erCRMmqOnTp9veowMGDFA5OTl29QHVvn171bhxY9WuXTs1depU9cADD9i+5y+99JIClJ+fn5oyZYp6/PHHVZcuXVRkZKTq1KmTKvwxa7VaVbNmzZS7u7tKSkoqEtvKlSsVoB599NEyfc9E1ZDESNQY+YmRUkqtXbtWAeqWW26xq1NcYqSUUiNHjlSAWrZsWZHz5icbn3/+eYnXvtzEKCoqSlksliLl7733ngLUCy+8YFee/6FhNBqL/PKeOXOmAtSLL75oV57/Qd64cWMVHR1tKzebzbYPtG3bttkdc+zYMWW1WovE9eSTTypAffzxx3bl+R+Y7u7uas+ePUWOy9/v7e2t9u/fbyvPyspSbdu2VQaDQfn6+qrNmzfb9lmtVnXttdcqQP3555+28tTUVOXv76+MRqPaunWr3XXmzZunADVo0KDL/h4UlxgtXLhQAapHjx5FPqAsFos6e/ZskbYXp7TEyMvLS/311192++644w4FqNWrV9uV9+/f3+4DtDCz2ayaNWumXF1d1a+//mq378yZM6pBgwYqJCREZWZmFonhUj/H8sRY0fd4WROj/JimTZtmdx2LxWJLZr744gtbeX5iBKjZs2fbneuHH34oNhnPj2nEiBF236/C158/f75def41xo4dW+T3zrFjx5STk5MKDAxUJ0+etJVbrVZ1++23244t7OWXX1aAevPNN4t8D/r166c0TSvyB6GoXpIYiRqjcGKklFJXXnmlAuw+DEpKjH766SdbL0hhp06dUkajUYWGhhY5prDLTYxKYrValbe3txo4cKBdef4v6DFjxhQ55vjx47bepMLyk4LFixcXOeaDDz4o8ZdtceLj4xWg7r77brvywh9Oxcnf//TTTxfZN2fOHNsHyMWWLVumALV06VJb2YoVK4rtCVBK7z3I/1lHRUXZyivyPSguMcrv4SouaSiP0hKjp556qkj9jRs3FtsjUFpi9OWXXypAzZgxo9j9CxYsUID65ptvisRwqZ9jeWIsyaXe42VJjHJzc1VAQIAKCwsrNvlKTExUmqapESNG2MryE6PIyMhij2ncuLEKCAiwK+vcubMymUwqMTGxSH2LxaICAgLUFVdcYVcOKGdnZ3X+/Pkix8ydO1cBas6cOUX2RUVFKaPRWOTnmpCQoFxdXVWHDh3syvfv368AdfXVVxc5l6heMvla1FivvvoqvXv35tFHH2Xbtm2l3s589dVX06xZM3799VcOHTpEq1atAPjggw/Izc3l7rvvxsmp6t7uZrOZhQsXsnr1avbv309ycrLdnIgzZ84Ue9wVV1xRpCw8PByAxMTEyz4mPT2d119/nS+++ILDhw+TmpqKUuqScfXs2bPY8nwXT14HaNCgwSX3FZ678ueffwL6MgoXM5lM9O/fn+XLl/Pnn38SERFht78i37d86enp/PPPP4SEhNClS5dS616Oy4mxsD/++APQ59QUd2PAkSNHADh48CA33HCD3b5L/RzLE2NF3+NlcfjwYRISEmjRogVz584tto6bmxsHDx4sUt65c2eMRmOx7cj/3gFkZGSwb98+AgMDWbBgQbHXcHFxKfYakZGRBAcHFynPfw/37du3yL6IiAjCw8OLrDuWP49r2bJl/PHHH1x55ZWAPm8QYMqUKcXGJqqPJEaixrryyisZMWIEn376KZ988gmjRo0qsa6maUycOJEnn3ySxYsX8/LLL2O1Wvnggw/QNK3KJl3nGzVqFF988QVNmzZl2LBhhIaG4uLiAsCCBQtKnEjp4+NTpCw/gcvNzb2sY8xmM1dffTU7duygffv2jBo1iqCgIEwmEwBz5swpMa7Q0NCSmnrJGErbZzabbWXJycmlXissLMyuXlmvX9L3LV/+BO2GDRuWWu9yXU6MhSUkJACwdu3aUuulpaUVKbucn+PFMVb0PV4W+W08cuRIqTddFNfG4toAejsKJ26JiYkopYiLiyvzjR35Svo+5r83Q0JCit0fEhJS7IKs9913H8uWLeP999/nyiuvJCsri+XLlxMcHMwtt9xSrthE5ZPESNRoL774IuvWrePJJ5+85C+Mu+++m2eeeYbly5czb948Nm7cSHR0NNdcc02V3rK9a9cuvvjiC6655hq+//57W+IBYLVaeemll6rs2qVZt24dO3bsYNy4cUVWH46JiSn1w6E6FhvM/0A7d+5csftjYmLs6lUWX19f4PJ6OKpTfvvXrVvHTTfdVK5jK+vnWNXv8fw23nLLLXz++eeXda5LXaNLly7s2bOnXMeW9H309vYG4Pz587Rr167I/vPnzxd7XM+ePenWrRtr1qxh/vz5fPXVVyQmJjJz5ky7761wDLldX9RozZo14/777+fEiRO8+eabpdYNCQnhpptuIjY2lq+++sp2+/7kyZOrNMajR48CMGzYsCK/1Hbs2FHsbfHVIT+uW2+9tci+/NvDHSl/GGvz5s1F9lksFn777TeAEtemqigPDw/at2/P+fPn2bt3b6Weu6Lyh4KK60nq1asXAL/++mu1xlRYVb/HW7duja+vL9u2bbPrVaxMnp6etGvXjn///bfSlhfIfw/nv1cLi46O5tSpUyUee99995GZmcmKFStYuHAhmqbZlh4RjiWJkajxnnnmGXx9fXn++eeL7UovLP8Xy8svv8y6desICgri5ptvrtL48teGufgDPjY2lgceeKBKr12a/Lg2bdpkV378+HGeeOIJB0Rk7+abb8bf35+PP/6Ybdu22e1bsGABx48fZ9CgQTRu3LjSr/3QQw8B+odTSkqK3b7c3Fxbb1V1CQgIACj2g3TYsGE0a9aMt99+m++++67Y4//44w8yMjKqLL6qfo87OTnx4IMPEhMTw0MPPVRsohUTE8P+/fsv6zrTp08nJyeHe+65p9g1rxITE8vVmzR69GicnJx488037X52SimefPLJUodM77jjDnx9fXnhhRf4/fffufbaa2natGm52iOqhgyliRrP39+f//73vzz++OOXrHvttdfSpEkTtm/fDsC4ceNwdnau0vi6d+9Onz59+Pzzz+nduzd9+/bl/PnzfP/997Rq1co28bi6DR06lObNmzN//nz++ecfunTpwsmTJ/nmm2+44YYbOHnypEPiyufp6ckHH3zAbbfdRv/+/bntttto3Lgxu3fvZv369YSGhtompFa2iRMn8ttvv7F8+XKaN2/OsGHDCAoK4syZM2zatIl77rmnWldAv+aaa1i7di3Dhw/nuuuuw83NjYiICMaOHYvJZOLzzz9nyJAh3HDDDfTu3ZvOnTvj7u7OqVOn2LlzJ8ePHycmJqbKHk9SHe/xp59+mn379vHee+/x9ddfc/XVV9OwYUNiY2M5cuQIW7du5fnnn6dt27YVvsY999zD7t27eeedd2jWrBlDhgyhcePGXLhwgRMnTvDLL79w9913895775XpfM2aNePZZ5/lv//9L506dWLUqFH4+PiwYcMGLly4QKdOnfjrr7+KPdbd3Z1x48bx+uuvAzLpuiaRHiNRKzz00ENlekL6xROtJ06cWIVR6YxGI1999ZVtteY33niD3377jYkTJ/Ljjz86bM6Ah4cHGzduZPTo0fz777+88cYb/PXXXzz99NOsXLnSITFdbNiwYWzdupXrr7+eH3/8kVdeeYUDBw5w7733snv37ir7C1rTNJYtW8bKlStp06YNn3zyCa+99hq//PIL/fr1K/dcnsuVf+NAUlISL730Ek8//TRL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2geQlpbGvHnz6NixI15eXgQEBNC9e3fefvttzGazXd3a2D6AuLg4HnjgASIiInB2dqZBgwZMmjSJc+fOFalb49qoRKUBlK+vrxo4cKAKDQ1VRqOxxLoWi0X1799fAWrs2LFq0aJF6qGHHlJGo1ENGDBAWSwWu/p79+5Vnp6eKiAgQM2dO1e99957qm/fvgpQixcvruqmKaWUeuKJJ5SHh4e6/fbb1euvv67effddNXLkSAWoxo0bq3PnztXq9iml1JEjR9SVV16ppk+frl5//XW1ePFi9dRTT6lGjRopk8mk1q9fX+vbWNjDDz+sPD09FaAmTJhgt6+2tg9Q/fr1UytWrCiymc3mWt8+pZQ6ffq0atGihfL19VWPPPKIWrx4sXr99dfVAw88oJ577rla3cZ9+/YV+7N77rnnFKC6du1aq9tnNptVz549lcFgUOPHj1fvvfeeev3119WAAQMUoEaPHl2r26eUUrGxsapJkyZK0zQ1btw49d5776knnnhCeXp6qsjISHX+/Pka3UZJjCrR0aNHbV/379+/1MRoyZIlClAPPvigXfmCBQsUoD744AO78n79+ilN09TOnTttZWazWXXv3l35+PioxMTEymlEKXbu3Fnsdf7v//5PAeqxxx6zldXG9pXm9OnTymg0qoEDB9rKansbd+zYoYxGo3rttdeKTYxqa/sANW7cuEvWq63tU0qpq6++WoWEhKioqKhS69XmNl7sqaeeUoB67733bGW1sX0bNmxQgHr00UftynNzc9UVV1yhNE2zxVEb26eUUo888ogC1Lx58+zKt27dqjRNU5MnT7aV1cQ2SmJURS6VGOVnyBf/YsvMzFQeHh5qwIABtrITJ04owK4s34oVKxSgli5dWnnBl9O+ffsUoIYMGWIrq0vtU0r/q8bT01N17tzZVlab25iTk6M6duyobrrpJltsFydGtbV9+YlRdna2SklJKbFebW3fb7/9pgD12muvKaX092ZqamqxdWtrGy9msVhUw4YNlYeHh0pOTraV18b2rV27VgHq1VdfLbJv6NChymg0qoyMDKVU7WyfUkp16tRJASomJqbIvpYtWypPT0+VmZmplKqZbZQ5Rg6glGLXrl00aNCAiIgIu32urq507dqVnTt3ovKWmNq+fTsAvXv3LnKu/LL8Oo6QP94fHBwM1I32mc1m4uPjOXfuHDt2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WrduXWKd2ti+6OhoFRgYqAA1dOhQ9dZbb6mXXnpJdezYUQHqoYceqtXty9eqVSv1+OOPq/fff18tWLDAtl7R/fffb1evJrZREqNKlP+DL2m7WGpqqpoxY4Zq3LixMplMqnHjxmrGjBklLta2d+9edcMNNygfHx/l5uamevToodauXVvVzbIZN25cqe2LiIiwq1/b2qeUvirtiBEjVGRkpHJ3d1fOzs4qIiJCjR49Wm3fvr1I/drYxouVlBgpVfvat27dOjVkyBDVsGFD5eLiotzc3FT79u3VU089Vexij7WtfYV999136qqrrlIeHh62WD7++OMi9WprG1999dUSF0IsrDa278SJE+qee+5R4eHhysnJSbm7u6vu3bur999/X1mtVru6tbF9Sil11113qaZNmypXV1fl7e2t+vfvrz755JNi69a0NmpK5a2cJIQQQghRz8nkayGEEEKIPJIYCSGEEELkkcRICCGEECKPJEZCCCGEEHkkMRJCCCGEyCOJkRBCCCFEHkmMhBBCCCHySGIkhBBCCJFHEiMhhBBCiDySGAkhRD01YMAANE2zbatXr77sc954441251y6dOnlBypENZLESIhaqPAHT1k2+XAqn82bN6NpGrNnz3Z0KNVi1qxZzJo1i/bt29uV5ydOmzdvLnJMeno6Q4YMQdM0hgwZQlpaGgCjR49m1qxZDBs2rDpCF6LSOTk6ACFE+c2aNatI2YIFC0hOTmbatGn4+vra7evcuXP1BCZqpfImgPHx8Vx//fXs3LmTO++8kw8//BCTyQToiRHA0qVLWbduXWWHKkSVk8RIiFqouA+ypUuXkpyczMMPP0xkZGS1xyTqh6ioKIYMGcLhw4eZPn06r7zyCpqmOTosISqNDKUJUQ9s376dESNGEBoairOzM+Hh4UyZMoWzZ88WqZs/fGI2m3n22Wdp1qwZrq6utGrVikWLFtnqvf3227Rv3x43NzcaNWrE7NmzsVqtdueKiopC0/6/vfuPqar84wD+vsD9JeGF5Cry25wVQQZIokRyLxY/BW1SORlewIFbc1qsFtZIaPWHZRP8o6CMH20gsrLN61JkjECWYmhBNk0WoFxREbn3IhqK8fn+IffG6VwI5zX50uf1D+PznOfHOWeDz87znOdIkJ6ejrNnz2LNmjV49NFH4ezsjMjISBw5cmTCMe/duxdarRZu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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.title('NVT anharmonic internal energy')\n", + "plt.xlabel('Temperatures [K]')\n", + "plt.ylabel('$U_{\\mathrm{ah}}$ [meV]')\n", + "plt.plot(temperatures, U_md_nvt, label='MD')\n", + "plt.fill_between(temperatures, U_md_nvt-U_md_nvt_err, U_md_nvt+U_md_nvt_err, alpha=0.5)\n", + "# plt.plot(temperatures, U_mf, label='mf')\n", + "# plt.plot(temperatures, U_mfc, label='mfc')\n", + "plt.plot(temperatures, U_mfcv, label='mfc-lc-v')\n", + "plt.plot(temperatures, U_mfcv_ps, label='mfc-lc-v-ps')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "ebcb8647-fabb-4ee4-953b-ef4ae24ab553", + "metadata": { + "ExecuteTime": { + "end_time": "2022-12-01T12:41:10.549882Z", + "start_time": "2022-12-01T12:41:10.318112Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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ftDl5JJ1GWvXrl251mVkZLB3716AfOemKi5bW1uaNGnC9evXOX78eIluu7DatWsHwJ9//lmo/m3atEGv1xe6f0nuuyzsw8HBgdq1a3PlypU8H4mT9buSFz8/Pzp16sT69evZt28fu3btIiAggAYNGpRIbaJskGAkKpV33nkHJycnZs2adc9LBVlzGn388cds3LgRNzc3BgwYUKr1ZY15+PcHfGRkJM8++2yp7rsgWXXt3LkzR3tQUBD/+c9/TFBRTgMGDMDFxYVVq1Zx4MCBHOvmzp1LUFAQPXr0oGbNmiW+76yzGFOmTMm+TJMlMzMz+2xVaRk3bhxOTk7MnDmTgwcP5lpvNBpz/D65u7szcuRIDh8+zHvvvZf9SJy7Xbp0icuXL99z3/3796dOnTp89dVX/P7773n22b9/P0lJSYU/oH9p1aoVnTt3Zv369Xz33Xd59jl16hSRkZGF3uaTTz6J0WjkjTfeyDFOLiwsjLlz5xb43meeeYbU1FSGDBmCUoqnn3660PsV5YNcShOViouLC//973957bXX7tm3V69e1KpVi7///huAMWPGYGFhUar1tW7dmo4dO7J+/Xo6dOhAp06duH79Ops2baJ+/fommxW5b9++1K1blzlz5nD69GmaN29OaGgov/76K4899hihoaEmqSuLnZ0d3333HUOHDiUgIIChQ4dSs2ZNjhw5wpYtW/D09My+7FHSJkyYwN69e1m2bBl169alf//+uLm5ceXKFXbu3MlTTz1VqjOgV6lShXXr1jFw4EDatWtH9+7dady4MXq9ntDQUPbv38/NmzdzPDPwyy+/5MKFC7zzzjssX76cTp064eHhwdWrVwkMDOTQoUOsWrWKWrVqFbhvc3Nz1q9fT+/evXnsscfo0KEDzZo1w8bGhrCwMA4dOkRQUBARERHY2NgU+xi///57HnroIcaPH8/nn39O27ZtcXJyIjw8nJMnT3L69Gn279+Pu7t7obb32muvsWHDBlavXs25c+fo1asXsbGxrFmzhi5durBhwwb0+rzPGwwePBh3d3ciIiJwc3Nj0KBBxT4uUTbJGSNR6bzwwguFekL6vwdaT5gwoRSr0hgMBn7++efs2Zo///xz9u7dy4QJE9i8eXOuy2sPiq2tLTt27GDEiBH8888/fP7555w8eZK3336bFStWmKSmf+vfvz9//fUXjz76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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "F_md_nvt, fine_temperatures = get_ah_F(U_md_nvt, temperatures=temperatures)\n", + "plt.title('NVT anharmonic free energy')\n", + "plt.xlabel('Temperatures [K]')\n", + "plt.ylabel('$F_{\\mathrm{ah}}$ [meV]')\n", + "plt.plot(fine_temperatures, F_md_nvt, label='MD')\n", + "# plt.plot(fine_temperatures, F_mf, label='mf')\n", + "# plt.plot(fine_temperatures, F_mfc, label='mfc')\n", + "plt.plot(fine_temperatures, F_mfcv, label='mfc-lc-v')\n", + "plt.plot(fine_temperatures, F_mfcv_ps, label='mfc-lc-v-ps')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98439c5b", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8 (XPython)", + "language": "python", + "name": "xpython" + }, + "language_info": { + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "version": "3.8.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyiron_contrib/atomistics/ml/__init__.py b/pyiron_contrib/atomistics/ml/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pyiron_contrib/atomistics/ml/potentialfit.py b/pyiron_contrib/atomistics/ml/potentialfit.py new file mode 100644 index 000000000..49c5b6009 --- /dev/null +++ b/pyiron_contrib/atomistics/ml/potentialfit.py @@ -0,0 +1,151 @@ +""" +Abstract base class for fitting interactomic potentials. +""" + +from typing import List + +import abc + +import pandas as pd +import matplotlib.pyplot as plt +import numpy as np + +from pyiron_base import FlattenedStorage +from pyiron_contrib.atomistics.atomistics.job.trainingcontainer import ( + TrainingContainer, + TrainingStorage, +) + + +class PotentialFit(abc.ABC): + """ + Abstract mixin that defines a general interface to potential fitting codes. + + Training data can be added to the job with :method:`~.add_training_data`. This should be atom structures with + (at least) corresponding energies and forces, but additional (per structure or atom) maybe added. Subclasses of + :class:`~.TrainingContainer` that define and handle such data are explicitly allowed. + + :property:`~.training_data` and :property:`~.predicted_data` can be used to access the initial training data and the + predicted data on them after the fit. + """ + + @abc.abstractmethod + def _add_training_data(self, container: TrainingContainer) -> None: + pass + + def add_training_data(self, container: TrainingContainer) -> None: + """ + Add data to the fit. + + Calling this multiple times appends data to internal storage. + + Args: + container (:class:`.TrainingContainer`): container holding data to fit + """ + if self.status.initialized: + self._add_training_data(container) + else: + raise ValueError("Data can only be added before fitting is started!") + + @abc.abstractmethod + def _get_training_data(self) -> TrainingStorage: + pass + + @property + def training_data(self) -> TrainingStorage: + """ + Return all training data added so far. + + Returns: + :class:`pyiron_contrib.atomistics.atomistics.job.trainingcontainer.TrainingStorage`: container holding all training data + """ + return self._get_training_data() + + @abc.abstractmethod + def _get_predicted_data(self) -> FlattenedStorage: + pass + + @property + def predicted_data(self) -> FlattenedStorage: + """ + Predicted properties of the training data after the fit. + + In contrast to :property:`~.training_data` this may not contain the original atomic structures, but must be in + the same order. Certain properties in the training data may be omitted from this data set, if the inconvenient + or impossible to predict. This should be documented on the subclass for each specific code. + + Returns: + :class:`pyiron_base.FlattenedStorage`: container holding all predictions of the fitted potential on the + training data + """ + if self.status.finished: + return self._get_predicted_data() + else: + raise ValueError("Data can only be accessed after successful fit!") + + @property + def plot(self): + """ + Plots correlation and (training) error histograms. + """ + return PotentialPlots(self.training_data, self.predicted_data) + + @abc.abstractmethod + def get_lammps_potential(self) -> pd.DataFrame: + """ + Return a pyiron compatible dataframe that defines a potential to be used with a Lammps job (or subclass + thereof). + + Returns: + DataFrame: contains potential information to be used with a Lammps job. + """ + pass + + +class PotentialPlots: + def __init__(self, training_data, predicted_data): + self._training_data = training_data + self._predicted_data = predicted_data + + def energy_scatter_histogram(self): + """ + Plots correlation and (training) error histograms. + """ + energy_train = self._training_data["energy"] / self._training_data["length"] + energy_pred = self._predicted_data["energy"] / self._predicted_data["length"] + plt.subplot(1, 2, 1) + plt.scatter(energy_train, energy_pred) + + plt.xlabel("True Energy Per Atom [eV / atom]") + plt.ylabel("Predicted Energy Per Atom [eV / atom]") + plt.plot() + + plt.subplot(1, 2, 2) + plt.hist(energy_train - energy_pred) + plt.xlabel("Training Error [eV / atom]") + + def force_scatter_histogram(self, axis=None): + """ + Plots correlation and (training) error histograms. + + Args: + axis (None, int): Whether to plot for an axis or norm + + """ + force_train = self._training_data["forces"] + force_pred = self._predicted_data["forces"] + + if axis is None: + ft = np.linalg.norm(force_train, axis=1) + fp = np.linalg.norm(force_pred, axis=1) + else: + ft = force_train[:, axis] + fp = force_pred[:, axis] + + plt.subplot(1, 2, 1) + plt.scatter(ft, fp) + plt.xlabel("True Forces [eV/$\mathrm{\AA}$]") + plt.ylabel("Predicted Forces [eV/$\AA$]") + plt.subplot(1, 2, 2) + plt.hist(ft - fp) + plt.xlabel("Training Error [eV/$\AA$]") diff --git a/pyiron_contrib/atomistics/mlip/lammps.py b/pyiron_contrib/atomistics/mlip/lammps.py index 7fcc16020..bb8a6cd87 100644 --- a/pyiron_contrib/atomistics/mlip/lammps.py +++ b/pyiron_contrib/atomistics/mlip/lammps.py @@ -3,9 +3,12 @@ # Distributed under the terms of "New BSD License", see the LICENSE file. import os -from pyiron.lammps.base import Input -from pyiron.lammps.interactive import LammpsInteractive +from pyiron_atomistics.lammps.base import Input +from pyiron_atomistics.lammps.interactive import LammpsInteractive +from pyiron_atomistics.atomistics.structure.atoms import Atoms +from pyiron_atomistics.atomistics.structure.structurestorage import StructureStorage from pyiron_contrib.atomistics.mlip.mlip import read_cgfs +from pyiron_contrib.atomistics.mlip.cfgs import loadcfgs from pyiron_base import GenericParameters __author__ = "Jan Janssen" @@ -26,6 +29,7 @@ def __init__(self, project, job_name): self.__version__ = None # Reset the version number to the executable is set automatically self._executable = None self._executable_activate() + self._selected_structures = None def set_input_to_read_only(self): """ @@ -41,24 +45,70 @@ def write_input(self): self.input.mlip['mtp-filename'] = os.path.basename(self.potential['Filename'][0][0]) self.input.mlip.write_file(file_name="mlip.ini", cwd=self.working_directory) - def enable_active_learning(self): - self.input.mlip.load_string("""\ + def convergence_check(self): + for line in self["error.out"]: + if line.startswith("MLIP: Breaking threshold exceeded"): return False + return True + + def enable_active_learning(self, threshold=2.0, threshold_break=5.0): + """ + Enable active learning during MD run. + + Automatically collect structures on which the potential is extrapolating. + + Args: + threshold (float): select structures with extrapolation grade larger than this + threshold_break (float): stop the MD run after seeing a structure with extrapolation grade larger than this + """ + self.executable.accepted_return_codes += [8] + self.input.mlip.load_string(f"""\ mtp-filename auto calculate-efs TRUE select TRUE -select:threshold 2.0 -select:threshold-break 5.0 +select:threshold {threshold} +select:threshold-break {threshold_break} select:save-selected selected.cfg select:load-state state.mvs select:log selection.log write-cfgs:skip 0 """) + def _get_selection_file(self): + return os.path.join(self.working_directory, self.input.mlip['select:save-selected']) + + @property + def _selection_enabled(self): + return self.input.mlip["select"] == "TRUE" + + @property + def selected_structures(self): + """ + :class:`.StructureStorage`: structures that the potential extrapolated on during the run. + + Only available if :method:`.enable_active_learning` was called and once the job has been collected. + """ + if not (self.status.collect or self.status.finished or self.status.not_converged): + raise ValueError("Selected structures are only available once the job has finished!") + if not self._selection_enabled: + raise ValueError("Selected structures are only available after calling enable_active_learning()!") + if self._selected_structures is None: + if "selected" in self["output"].list_groups(): + self._selected_structures = self["output/selected"].to_object() + else: + self._selected_structures = StructureStorage() + return self._selected_structures + def collect_output(self): super(LammpsMlip, self).collect_output() if 'select:save-selected' in self.input.mlip._dataset['Parameter']: - file_name = os.path.join(self.working_directory, self.input.mlip['select:save-selected']) + file_name = self._get_selection_file() if os.path.exists(file_name): + for cfg in loadcfgs(file_name): + self.selected_structures.add_structure( + Atoms(species=self.structure.species, indices=cfg.types, positions=cfg.pos, cell=cfg.lat, + pbc=[True, True, True]), + mv_grade=cfg.grade + ) cell, positions, forces, stress, energy, indicies, grades, jobids, timesteps = read_cgfs(file_name=file_name) with self.project_hdf5.open("output/mlip") as hdf5_output: hdf5_output['forces'] = forces @@ -67,6 +117,7 @@ def collect_output(self): hdf5_output['cells'] = cell hdf5_output['positions'] = positions hdf5_output['indicies'] = indicies + self.selected_structures.to_hdf(self.project_hdf5.open("output"), "selected") class MlipInput(Input): diff --git a/pyiron_contrib/atomistics/mlip/mlip.py b/pyiron_contrib/atomistics/mlip/mlip.py index a661c59bb..72e26670d 100644 --- a/pyiron_contrib/atomistics/mlip/mlip.py +++ b/pyiron_contrib/atomistics/mlip/mlip.py @@ -10,9 +10,10 @@ import pandas as pd import posixpath import scipy.constants +from pyiron_atomistics.atomistics.structure.structurestorage import StructureStorage from pyiron_base import state, GenericParameters, GenericJob, Executable, FlattenedStorage from pyiron_atomistics import ase_to_pyiron, Atoms -from pyiron_atomistics.atomistics.structure.structurestorage import StructureStorage +from pyiron_contrib.atomistics.ml.potentialfit import PotentialFit from pyiron_contrib.atomistics.mlip.cfgs import savecfgs, loadcfgs, Cfg from pyiron_contrib.atomistics.mlip.potential import MtpPotential @@ -28,8 +29,7 @@ gpa_to_ev_ang = 1e22 / scipy.constants.physical_constants['joule-electron volt relationship'][0] - -class Mlip(GenericJob): +class Mlip(GenericJob, PotentialFit): def __init__(self, project, job_name): super(Mlip, self).__init__(project, job_name) self.__version__ = '0.1.0' @@ -68,6 +68,22 @@ def potential_files(self): if os.path.exists(pot) and os.path.exists(states): return [pot, states] + def _get_elements(self): + """ + Return elements in training in insertion order, i.e. elements seen earlier get lower indices. + """ + elements = [] + for job_id in self._job_dict: + j = self.project.inspect(job_id) + if j["NAME"] == "TrainingContainer": + candidates = j.to_object().get_elements() + else: + candidates = j["input/structure/species"] + for e in candidates: + if e not in elements: + elements.append(e) + return elements + def potential_dataframe(self, elements=None): """ :class:`pandas.DataFrame`: potential dataframe for lammps jobs @@ -84,22 +100,24 @@ def potential_dataframe(self, elements=None): if elements is None: elements = self.input["species"] if elements is None: - raise ValueError("elements not defined in input, elements must be explicitely passed!") + elements = self._get_elements() # AAAH if self.status.finished: return pd.DataFrame({ "Name": ["".join(elements)], - "Filename": [[self.potential_files[0]]], + "Filename": [self.potential_files], "Model": ["Custom"], "Species": [elements], "Config": [["pair_style mlip mlip.ini\n", "pair_coeff * *\n" ]] }) + else: + raise ValueError(f"Potential only available after job is finished, not {self.status}!") @property def potential(self): - if self.status.finished: + if self.status.finished and self._potential is not None: return self._potential else: raise ValueError("potential only available on successfully finished jobs") @@ -145,10 +163,9 @@ def write_input(self): species = np.array(species) input_store = StructureStorage() - input_store.add_array('energy', dtype=np.float64, shape=(1,), per='chunk') + input_store.add_array('energy', dtype=np.float64, shape=(), per='chunk') input_store.add_array('forces', dtype=np.float64, shape=(3,), per='element') input_store.add_array('stress', dtype=np.float64, shape=(6,), per='chunk') - input_store.add_array('grade', dtype=np.float64, shape=(1,), per='chunk') for cfg in loadcfgs(os.path.join(self.working_directory, "training.cfg")): struct = Atoms(symbols=species[np.cast[np.int64](cfg.types)], positions=cfg.pos, cell=cfg.lat, pbc=[True]*3) # HACK for pbc input_store.add_structure(struct, identifier=cfg.desc, @@ -177,26 +194,28 @@ def collect_output(self): _, _, _, _, _, _, grades_lst, job_id_grades_lst, timestep_grades_lst = read_cgfs(file_name) else: grades_lst, job_id_grades_lst, timestep_grades_lst = [], [], [] - self._potential.load(os.path.join(self.working_directory, "Trained.mtp_")) + try: + self._potential.load(os.path.join(self.working_directory, "Trained.mtp_")) + except: + self.logger.warn('Failed to parse potential file! job.potential will not be available.') + self._potential = None training_store = FlattenedStorage() - training_store.add_array('energy', dtype=np.float64, shape=(1,), per='chunk') + training_store.add_array('energy', dtype=np.float64, shape=(), per='chunk') training_store.add_array('forces', dtype=np.float64, shape=(3,), per='element') training_store.add_array('stress', dtype=np.float64, shape=(6,), per='chunk') - training_store.add_array('grade', dtype=np.float64, shape=(1,), per='chunk') for cfg in loadcfgs(os.path.join(self.working_directory, "training_efs.cfg")): training_store.add_chunk(len(cfg.pos), identifier=cfg.desc, - energy=cfg.energy, forces=cfg.forces, stress=cfg.stresses, grade=cfg.grade + energy=cfg.energy, forces=cfg.forces, stress=cfg.stresses ) testing_store = FlattenedStorage() - testing_store.add_array('energy', dtype=np.float64, shape=(1,), per='chunk') + testing_store.add_array('energy', dtype=np.float64, shape=(), per='chunk') testing_store.add_array('forces', dtype=np.float64, shape=(3,), per='element') testing_store.add_array('stress', dtype=np.float64, shape=(6,), per='chunk') - testing_store.add_array('grade', dtype=np.float64, shape=(1,), per='chunk') for cfg in loadcfgs(os.path.join(self.working_directory, "testing_efs.cfg")): testing_store.add_chunk(len(cfg.pos), identifier=cfg.desc, - energy=cfg.energy, forces=cfg.forces, stress=cfg.stresses, grade=cfg.grade + energy=cfg.energy, forces=cfg.forces, stress=cfg.stresses ) with self.project_hdf5.open('output') as hdf5_output: @@ -207,7 +226,8 @@ def collect_output(self): hdf5_output['timestep_diff'] = timestep_diff_lst hdf5_output['job_id_new'] = job_id_new_training_lst hdf5_output['timestep_new'] = timestep_new_training_lst - self._potential.to_hdf(hdf=hdf5_output) + if self._potential is not None: + self._potential.to_hdf(hdf=hdf5_output) training_store.to_hdf(hdf=hdf5_output, group_name="training_efs") testing_store.to_hdf(hdf=hdf5_output, group_name="testing_efs") @@ -356,6 +376,8 @@ def _write_configurations(self, file_name='training.cfg', cwd=None, respect_step if job._container.has_array("stress"): volume = np.abs(np.linalg.det(cell_lst[-1])) stress_lst.append(job._container.get_array("stress", time_step) * volume) + if np.isnan(stress_lst[-1]).any(): + stress_lst[-1] = None track_lst.append(str(ham.job_id) + '_' + str(time_step)) continue original_dict = {el: ind for ind, el in enumerate(sorted(ham['input/structure/species']))} @@ -459,6 +481,21 @@ def _find_potential(potential_name): raise ValueError('Potential not found!') + # PotentialFit Implementation + def _add_training_data(self, container): + self.add_job_to_fitting(container.id, 0, container.number_of_structures - 1, 1) + + def _get_training_data(self): + # TODO/BUG: only works after input is written for now, instead this should go over _job_ + return self["input/training_data"].to_object() + + def _get_predicted_data(self): + return self["output/training_efs"].to_object() + + def get_lammps_potential(self): + return self.potential_dataframe() + + class MlipParameter(GenericParameters): def __init__(self, separator_char=' ', comment_char='#', table_name="mlip_inp"): super(MlipParameter, self).__init__(separator_char=separator_char, comment_char=comment_char, diff --git a/pyiron_contrib/atomistics/mlip/parser.py b/pyiron_contrib/atomistics/mlip/parser.py index fe13daa42..ca54d070d 100644 --- a/pyiron_contrib/atomistics/mlip/parser.py +++ b/pyiron_contrib/atomistics/mlip/parser.py @@ -63,8 +63,8 @@ def make_list(field_expr, grouped=False): + pp.IndentedBlock( radial_func_types \ + pp.IndentedBlock(radial_func_coeffs + NL)[1, ...] - ) - )[1, ...] + )[1, ...] + ) radial_funcs = radial_funcs.set_results_name("funcs") radial_funcs = pp.ungroup(radial_funcs).set_results_name("funcs") diff --git a/pyiron_contrib/atomistics/pacemaker/__init__.py b/pyiron_contrib/atomistics/pacemaker/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pyiron_contrib/atomistics/pacemaker/job.py b/pyiron_contrib/atomistics/pacemaker/job.py new file mode 100644 index 000000000..74bb2d3e1 --- /dev/null +++ b/pyiron_contrib/atomistics/pacemaker/job.py @@ -0,0 +1,388 @@ +# coding: utf-8 +# Copyright (c) ICAMS, Ruhr University Bochum, 2022 + +## Executable required: $pyiron/resources/pacemaker/bin/run_pacemaker_tf_cpu.sh AND run_pacemaker_tf.sh + +import logging +from typing import List + +import numpy as np +import os +import pandas as pd +import ruamel.yaml as yaml + +from shutil import copyfile + +from pyiron_base import GenericJob, GenericParameters, state, Executable, FlattenedStorage + +from pyiron_contrib.atomistics.atomistics.job.trainingcontainer import TrainingStorage, TrainingContainer +from pyiron_contrib.atomistics.ml.potentialfit import PotentialFit + +from pyiron_atomistics.atomistics.structure.atoms import Atoms as pyironAtoms, ase_to_pyiron +from ase.atoms import Atoms as aseAtoms + +s = state.settings + + +class PacemakerJob(GenericJob, PotentialFit): + + def __init__(self, project, job_name): + super().__init__(project, job_name) + self.__name__ = "Pacemaker2022" + self.__version__ = "0.2" + + self._train_job_id_list = [] + + self.input = GenericParameters(table_name="input") + self._cutoff = 7.0 + self.input['cutoff'] = self._cutoff + self.input['metadata'] = {'comment': 'pyiron-generated fitting job'} + + # data_config + self.input['data'] = {} + # potential_config + self.input['potential'] = { + "elements": [], + "bonds": { + "ALL": { + "radbase": "SBessel", + "rcut": self._cutoff, + "dcut": 0.01, + "radparameters": [5.25] + } + }, + + "embeddings": { + "ALL": { + "fs_parameters": [1, 1, 1, 0.5], + "ndensity": 2, + "npot": "FinnisSinclairShiftedScaled" + } + }, + + "functions": { + "ALL": { + "nradmax_by_orders": [15, 3, 2, 1], + "lmax_by_orders": [0, 3, 2, 1], + } + } + } + + # fit_config + self.input['fit'] = { + "loss": {"L1_coeffs": 1e-8, "L2_coeffs": 1e-8, "kappa": 0.3, "w0_rad": 0, + "w1_rad": 0, "w2_rad": 0}, + "maxiter": 1000, + "optimizer": "BFGS", + "fit_cycles": 1 + } + self.input['backend'] = {"batch_size": 100, + "display_step": 50, + "evaluator": "tensorpot"} # backend_config + + self.structure_data = None + self._executable = None + self._executable_activate() + + state.publications.add(self.publication) + + @property + def elements(self): + return self.input["potential"].get("elements") + + @elements.setter + def elements(self, val): + self.input["potential"]["elements"] = val + + @property + def cutoff(self): + return self._cutoff + + @cutoff.setter + def cutoff(self, val): + self._cutoff = val + self.input["cutoff"] = self._cutoff + self.input["potential"]["bonds"]["ALL"]["rcut"] = self._cutoff + + @property + def publication(self): + return { + "pacemaker": [ + { + "title": "Efficient parametrization of the atomic cluster expansion", + "journal": "Physical Review Materials", + "volume": "6", + "number": "1", + "year": "2022", + "doi": "10.1103/PhysRevMaterials.6.013804", + "url": "https://doi.org/10.1103/PhysRevMaterials.6.013804", + "author": ["Anton Bochkarev", "Yury Lysogorskiy", "Sarath Menon", "Minaam Qamar", "Matous Mrovec", + "Ralf Drautz"], + }, + + { + "title": "Performant implementation of the atomic cluster expansion (PACE) and application to copper and silicon", + "journal": "npj Computational Materials", + "volume": "7", + "number": "1", + "year": "2021", + "doi": "10.1038/s41524-021-00559-9", + "url": "https://doi.org/10.1038/s41524-021-00559-9", + "author": ["Yury Lysogorskiy", "Cas van der Oord", "Anton Bochkarev", "Sarath Menon", + "Matteo Rinaldi", + "Thomas Hammerschmidt", "Matous Mrovec", "Aidan Thompson", "Gábor Csányi", + "Christoph Ortner", + "Ralf Drautz"], + }, + { + "title": "Atomic cluster expansion for accurate and transferable interatomic potentials", + "journal": "Physical Review B", + "volume": "99", + "year": "2019", + "doi": "10.1103/PhysRevB.99.014104", + "url": "https://doi.org/10.1103/PhysRevB.99.014104", + "author": ["Ralf Drautz"], + }, + ] + } + + def _save_structure_dataframe_pckl_gzip(self, df): + + if "NUMBER_OF_ATOMS" not in df.columns and "number_of_atoms" in df.columns: + df.rename(columns={"number_of_atoms": "NUMBER_OF_ATOMS"}, inplace=True) + df["NUMBER_OF_ATOMS"] = df["NUMBER_OF_ATOMS"].astype(int) + + # TODO: reference energy subtraction ? + if "energy_corrected" not in df.columns and "energy" in df.columns: + df.rename(columns={"energy": "energy_corrected"}, inplace=True) + + if "atoms" in df.columns: + # check if this is pyironAtoms -> aseAtoms + at = df.iloc[0]["atoms"] + if isinstance(at, pyironAtoms): + df["ase_atoms"] = df["atoms"].map(lambda s: s.to_ase()) + df.drop(columns=["atoms"], inplace=True) + else: + assert isinstance(at, aseAtoms), "'atoms' column is not a valid ASE Atoms object" + df.rename(columns={"atoms": "ase_atom"}, inplace=True) + elif "ase_atoms" not in df.columns: + raise ValueError("DataFrame should contain 'atoms' (pyiron Atoms) or 'ase_atoms' (ASE atoms) columns") + + if "stress" in df.columns: + df.drop(columns=["stress"], inplace=True) + + if "pbc" not in df.columns: + df["pbc"] = df["ase_atoms"].map(lambda atoms: np.all(atoms.pbc)) + + data_file_name = os.path.join(self.working_directory, "df_fit.pckl.gzip") + logging.info("Saving training structures dataframe into {} with pickle protocol = 4, compression = gzip".format( + data_file_name)) + df.to_pickle(data_file_name, compression="gzip", protocol=4) + return data_file_name + + def write_input(self): + # prepare datafile + if self._train_job_id_list and self.structure_data is None: + train_df = self.create_training_dataframe(self._train_job_id_list) + self.structure_data = train_df + + if isinstance(self.structure_data, pd.DataFrame): + logging.info("structure_data is pandas.DataFrame") + data_file_name = self._save_structure_dataframe_pckl_gzip(self.structure_data) + self.input["data"] = {"filename": data_file_name} + elements_set = set() + for at in self.structure_data["ase_atoms"]: + elements_set.update(at.get_chemical_symbols()) + elements = sorted(elements_set) + print("Set automatically determined list of elements: {}".format(elements)) + self.elements = elements + elif isinstance(self.structure_data, str): # filename + if os.path.isfile(self.structure_data): + logging.info("structure_data is valid file path") + self.input["data"] = {"filename": self.structure_data} + else: + raise ValueError("Provided structure_data filename ({}) doesn't exists".format(self.structure_data)) + elif hasattr(self.structure_data, "get_pandas"): # duck-typing check for TrainingContainer + logging.info("structure_data is TrainingContainer") + df = self.structure_data.to_pandas() + data_file_name = self._save_structure_dataframe_pckl_gzip(df) + self.input["data"] = {"filename": data_file_name} + elif self.structure_data is None: + raise ValueError( + "`structure_data` is none, but should be pd.DataFrame, TrainingContainer or valid pickle.gzip filename") + + metadata_dict = self.input["metadata"] + metadata_dict["pyiron_job_id"] = str(self.job_id) + + input_yaml_dict = { + "cutoff": self.input["cutoff"], + "metadata": metadata_dict, + "potential": self.input["potential"], + "data": self.input["data"], + "fit": self.input["fit"], + 'backend': self.input["backend"], + } + + if isinstance(self.input["potential"], str): + pot_file_name = self.input["potential"] + if os.path.isfile(pot_file_name): + logging.info("Input potential is filename") + pot_basename = os.path.basename(pot_file_name) + copyfile(pot_file_name, os.path.join(self.working_directory, pot_basename)) + input_yaml_dict['potential'] = pot_basename + else: + raise ValueError("Provided potential filename ({}) doesn't exists".format(self.input["potential"])) + + with open(os.path.join(self.working_directory, "input.yaml"), "w") as f: + yaml.dump(input_yaml_dict, f) + + def _analyse_log(self, logfile="metrics.txt"): + metrics_filename = os.path.join(self.working_directory, logfile) + + metrics_df = pd.read_csv(metrics_filename, sep="\s+") + res_dict = metrics_df.to_dict(orient="list") + return res_dict + + def collect_output(self): + final_potential_filename_yaml = self.get_final_potential_filename() + with open(final_potential_filename_yaml, "r") as f: + yaml_lines = f.readlines() + final_potential_yaml_string = "".join(yaml_lines) + + with open(self.get_final_potential_filename_ace(), "r") as f: + ace_lines = f.readlines() + final_potential_yace_string = "".join(ace_lines) + + with open(self.get_final_potential_filename_ace(), "r") as f: + yace_data = yaml.safe_load(f) + + elements_name = yace_data["elements"] + + with self.project_hdf5.open("output/potential") as h5out: + h5out["yaml"] = final_potential_yaml_string + h5out["yace"] = final_potential_yace_string + h5out["elements_name"] = elements_name + + log_res_dict = self._analyse_log() + + with self.project_hdf5.open("output/log") as h5out: + for key, arr in log_res_dict.items(): + h5out[key] = arr + + # training data + training_data_fname = os.path.join(self.working_directory, "fitting_data_info.pckl.gzip") + df = pd.read_pickle(training_data_fname, compression="gzip") + df["atoms"] = df.ase_atoms.map(ase_to_pyiron) + training_data_ts = TrainingStorage() + for _, r in df.iterrows(): + training_data_ts.add_structure(r.atoms, + energy=r.energy_corrected, + forces=r.forces, + identifier=r['name']) + + # predicted data + predicted_fname = os.path.join(self.working_directory, "train_pred.pckl.gzip") + df = pd.read_pickle(predicted_fname, compression="gzip") + predicted_data_fs = FlattenedStorage() + predicted_data_fs.add_array('energy', dtype=np.float64, shape=(), per='chunk') + predicted_data_fs.add_array('energy_true', dtype=np.float64, shape=(), per='chunk') + + predicted_data_fs.add_array('number_of_atoms', dtype=np.int, shape=(), per='chunk') + + predicted_data_fs.add_array('forces', dtype=np.float64, shape=(3,), per='element') + predicted_data_fs.add_array('forces_true', dtype=np.float64, shape=(3,), per='element') + for i, r in df.iterrows(): + identifier = r['name'] if "name" in r else str(i) + predicted_data_fs.add_chunk(r["NUMBER_OF_ATOMS"], identifier=identifier, + energy=r.energy_pred, + forces=r.forces_pred, + energy_true=r.energy_corrected, + forces_true=r.forces, + number_of_atoms = r.NUMBER_OF_ATOMS, + + energy_per_atom = r.energy_pred / r.NUMBER_OF_ATOMS, + energy_per_atom_true=r.energy_corrected / r.NUMBER_OF_ATOMS, + ) + + with self.project_hdf5.open("output") as hdf5_output: + training_data_ts.to_hdf(hdf=hdf5_output, group_name="training_data") + predicted_data_fs.to_hdf(hdf=hdf5_output, group_name="predicted_data") + + def get_lammps_potential(self): + elements_name = self["output/potential/elements_name"] + elem = " ".join(elements_name) + pot_file_name = self.get_final_potential_filename_ace() + pot_dict = { + 'Config': [["pair_style pace\n", "pair_coeff * * {} {}\n".format(pot_file_name, elem)]], + 'Filename': [""], + 'Model': ["ACE"], + 'Name': [self.job_name], + 'Species': [elements_name] + } + + ace_potential = pd.DataFrame(pot_dict) + + return ace_potential + + def to_hdf(self, hdf=None, group_name=None): + super().to_hdf( + hdf=hdf, + group_name=group_name + ) + with self.project_hdf5.open("input") as h5in: + self.input.to_hdf(h5in) + + def from_hdf(self, hdf=None, group_name=None): + super().from_hdf( + hdf=hdf, + group_name=group_name + ) + with self.project_hdf5.open("input") as h5in: + self.input.from_hdf(h5in) + + def get_final_potential_filename(self): + return os.path.join(self.working_directory, "output_potential.yaml") + + def get_final_potential_filename_ace(self): + return os.path.join(self.working_directory, "output_potential.yace") + + def get_current_potential_filename(self): + return os.path.join(self.working_directory, "interim_potential_0.yaml") + + # To link to the executable from the notebook + def _executable_activate(self, enforce=False, codename="pacemaker"): + if self._executable is None or enforce: + self._executable = Executable( + codename=codename, module="pacemaker", path_binary_codes=state.settings.resource_paths + ) + + def _add_training_data(self, container: TrainingContainer) -> None: + self.add_job_to_fitting(container.id) + + def add_job_to_fitting(self, job_id, *args, **kwargs): + self._train_job_id_list.append(job_id) + + def _get_training_data(self) -> TrainingStorage: + return self["output/training_data"].to_object() + + def _get_predicted_data(self) -> FlattenedStorage: + return self["output/predicted_data"].to_object() + + # copied/adapted from mlip.py + def create_training_dataframe(self, _train_job_id_list: List = None) -> pd.DataFrame: + if _train_job_id_list is None: + _train_job_id_list = self._train_job_id_list + df_list = [] + for job_id in _train_job_id_list: + ham = self.project.inspect(job_id) + if ham.__name__ == "TrainingContainer": + job = ham.to_object() + data_df = job.to_pandas() + df_list.append(data_df) + else: + raise NotImplementedError("Currently only TrainingContainer is supported") + + total_training_df = pd.concat(df_list, axis=0) + total_training_df.reset_index(drop=True, inplace=True) + + return total_training_df diff --git a/pyiron_contrib/atomistics/runner/job.py b/pyiron_contrib/atomistics/runner/job.py index 57f40e4c9..254399f0a 100644 --- a/pyiron_contrib/atomistics/runner/job.py +++ b/pyiron_contrib/atomistics/runner/job.py @@ -1,235 +1,609 @@ # coding: utf-8 -# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department +# Copyright (c) Georg-August-Universität Göttingen - Behler Group # Distributed under the terms of "New BSD License", see the LICENSE file. +"""Pyiron Hamiltonian for machine-learning with RuNNer. -""" -Demonstrator job for the RuNNer Neural Network Potential. -""" +The RuNNer Neural Network Energy Representation is a framework for the +construction of high-dimensional neural network potentials developed in the +group of Prof. Dr. Jörg Behler at Georg-August-Universität Göttingen. -import os.path -from glob import glob +Attributes: + RunnerFit : GenericJob, HasStorage, PotentialFit + Job class for generating and evaluating potential energy surfaces using + RuNNer. -from pyiron_base import state, GenericJob, Executable, DataContainer +.. _RuNNer online documentation: + https://theochem.gitlab.io/runner +""" + +from typing import Optional, List +from copy import deepcopy import numpy as np import pandas as pd -__author__ = "Marvin Poul" -__copyright__ = "Copyright 2020, Max-Planck-Institut für Eisenforschung GmbH - " \ - "Computational Materials Design (CM) Department" -__version__ = "0.1" -__maintainer__ = "Marvin Poul" -__email__ = "poul@mpie.de" -__status__ = "development" -__date__ = "March 3, 2021" +from ase.data import atomic_numbers +from ase.units import Bohr + +from runnerase.io.ase import (read_results_mode1, read_results_mode2, + read_results_mode3) +from runnerase import Runner +from runnerase.defaultoptions import DEFAULT_PARAMETERS + +from pyiron import Project +from pyiron_base import ProjectHDFio, FlattenedStorage +from pyiron_base import state, Executable, GenericJob, DataContainer +from pyiron_base import HasStorage + +from pyiron_contrib.atomistics.ml.potentialfit import PotentialFit +from pyiron_contrib.atomistics.atomistics.job import (TrainingContainer, + TrainingStorage) + +from .utils import container_to_ase +from .storageclasses import (HDFSymmetryFunctionSet, HDFSymmetryFunctionValues, + HDFSplitTrainTest, HDFFitResults, HDFWeights, + HDFScaling) + +__author__ = 'Alexander Knoll' +__maintainer__ = 'Alexander Knoll' +__email__ = 'alexander.knoll@chemie.uni-goettingen.de' +__copyright__ = 'Copyright 2022, Georg-August-Universität Göttingen - Behler '\ + 'Group' +__version__ = '0.1.1' +__status__ = 'development' +__date__ = 'May 17, 2022' + + +class RunnerFit(GenericJob, HasStorage, PotentialFit): + """Generate a potential energy surface using RuNNer. + + The RuNNer Neural Network Energy Representation (RuNNer) is a Fortran code + for the generation of high-dimensional neural network potentials actively + developed in the group of Prof. Dr. Jörg Behler at Georg-August-Universität + Göttingen. + + RuNNer operates in three different modes: + + - Mode 1: Calculation of symmetry function values. Symmetry functions + are many-body descriptors for the chemical environment of an atom. + - Mode 2: Fitting of the potential energy surface. + - Mode 3: Prediction. Use the previously generated high-dimensional + potential energy surface to predict the energy and force of an + unknown chemical configuration. + + The different modes generate a lot of output: + + - Mode 1: + - sfvalues: The values of the symmetry functions for each atom. + - splittraintest: which structures belong to the training and which + to the testing set. ASE needs this information to generate the + relevant input files for RuNNer Mode 2. + + - Mode 2: + - weights: The neural network weights. + - scaling: The symmetry function scaling factors. + + - Mode 3: + - energy + - forces -AngstromToBohr = 1.88972612 -ElectronVoltToHartree = 0.03674932218 + Examples: + Starting a new calculation (Mode 1): + ```python + from pyiron import Project + from job import RunnerFit + from runnerase import read_runnerase + + # Create an empty sample project and a new job. + pr = Project(path='example') + job = pr.create_job(RunnerFit, 'mode1') + + # Import RuNNer settings from RuNNer input.nn file using ASE's I/O + # routines. + options = read_runnerconfig('./') + + # Read input structures from a TrainingContainer that was saved before. + container = pr['H2O_MD'] + + # Attach the information to the job. + job.add_training_data = container + job.parameters.update(options) + + # Set the RuNNer Mode to 1. + job.input.runner_mode = 1 + + job.run() + ``` + + Restarting Mode 1 and running Mode 2: + + ```python + job = Project('example')['mode1'].restart('mode2') + job.parameters.runner_mode = 2 + job.run() + ``` + + Restarting Mode 2 and running Mode 3: + + ```python + job = Project('runnertest')['mode2'].restart('mode3') + job.parameters.runner_mode = 3 + job.run() + ``` + """ + + __name__ = 'RuNNerFit' + + # These properties are needed by RuNNer as input data (depending on the + # chosen RuNNer mode). + _input_properties = ['scaling', 'weights', 'sfvalues', 'splittraintest'] + + # Define a default executable. + _executable = Executable( + codename='runner', + module='runner', + path_binary_codes=state.settings.resource_paths + ) + + def __init__(self, project: Project, job_name: str) -> None: + """Initialize the class. + + Args: + project (Project): The project container where the job is created. + job_name (str): The label of the job (used for all directories). + """ + # Initialize first the job, then the job storage. + GenericJob.__init__(self, project=project, job_name=job_name) + HasStorage.__init__(self) -class RunnerFit(GenericJob): - def __init__(self, project, job_name): - super().__init__(project, job_name) - self._training_ids = [] + # Create groups for storing calculation inputs and outputs. + self.storage.create_group('input') + self.storage.create_group('output') - self.input = DataContainer(table_name="input") - self.input.element = "Cu" + # Create a group for storing the RuNNer configuration parameters. + self.storage.input.create_group('parameters') + + self.storage.input.parameters.update(deepcopy(DEFAULT_PARAMETERS)) + + # Store training data (structures, energies, ...) in a separate node. + self.storage.input.training_data = TrainingStorage() state.publications.add(self.publication) @property def publication(self): + """Define relevant publications.""" return { - "runner": [ + 'runner': [ { - "title": "First Principles Neural Network Potentials for Reactive Simulations of Large Molecular and Condensed Systems", - "journal": "Angewandte Chemie International Edition", - "volume": "56", - "number": "42", - "year": "2017", - "issn": "1521-3773", - "doi": "10.1002/anie.201703114", - "url": "https://doi.org/10.1002/anie.201703114", - "author": ["Jörg Behler"], + 'title': 'First Principles Neural Network Potentials for ' + 'Reactive Simulations of Large Molecular and ' + 'Condensed Systems', + 'journal': 'Angewandte Chemie International Edition', + 'volume': '56', + 'number': '42', + 'year': '2017', + 'issn': '1521-3773', + 'doi': '10.1002/anie.201703114', + 'url': 'https://doi.org/10.1002/anie.201703114', + 'author': ['Jörg Behler'], }, { - "title": "Constructing high‐dimensional neural network potentials: A tutorial review", - "journal": "International Journal of Quantum Chemistry", - "volume": "115", - "number": "16", - "year": "2015", - "issn": "1097-461X", - "doi": "10.1002/qua.24890", - "url": "https://doi.org/10.1002/qua.24890", - "author": ["Jörg Behler"], + 'title': 'Constructing high‐dimensional neural network' + 'potentials: A tutorial review', + 'journal': 'International Journal of Quantum Chemistry', + 'volume': '115', + 'number': '16', + 'year': '2015', + 'issn': '1097-461X', + 'doi': '10.1002/qua.24890', + 'url': 'https://doi.org/10.1002/qua.24890', + 'author': ['Jörg Behler'], }, { - "title": "Generalized Neural-Network Representation of High-Dimensional Potential-Energy Surfaces", - "journal": "Physical Review Letters", - "volume": "98", - "number": "14", - "year": "2007", - "issn": "1079-7114", - "doi": "10.1103/PhysRevLett.98.146401", - "url": "https://doi.org/10.1103/PhysRevLett.98.146401", - "author": ["Jörg Behler", "Michelle Parrinello"], + 'title': 'Generalized Neural-Network Representation of ' + 'High-Dimensional Potential-Energy Surfaces', + 'journal': 'Physical Review Letters', + 'volume': '98', + 'number': '14', + 'year': '2007', + 'issn': '1079-7114', + 'doi': '10.1103/PhysRevLett.98.146401', + 'url': 'https://doi.org/10.1103/PhysRevLett.98.146401', + 'author': ['Jörg Behler', 'Michelle Parrinello'], }, ] } - def add_job_to_fitting(self, job): + @property + def input(self) -> DataContainer: + """Show all input properties in storage. + + Returns: + self.storage.input (DataContainer): The data container with all + input properties. + """ + return self.storage.input + + @property + def output(self) -> DataContainer: + """Show all calculation output in storage. + + Returns: + self.storage.output (DataContainer): The data container with all + output properties. + """ + return self.storage.output + + @property + def parameters(self) -> DataContainer: + """Show the input parameters/settings in storage. + + Returns: + self.storage.input.parameters (DataContainer): The data container + with all input parameters for RuNNer. + """ + return self.storage.input.parameters + + @property + def scaling(self) -> Optional[HDFScaling]: + """Show the symmetry function scaling data in storage. + + Returns: + self.output.scaling (HDFScaling, None): If defined, the symmetry + function scaling data in storage. + """ + if 'scaling' in self.output: + return self.output.scaling + + return None + + @scaling.setter + def scaling(self, scaling: HDFScaling) -> None: + """Set the symmetry function scaling data in storage. + + Args: + scaling (HDFScaling): RuNNer symmetry function scaling data wrapped + in a HDFScaling storage container. + """ + self.output.scaling = scaling + + @property + def weights(self) -> Optional[HDFWeights]: + """Show the atomic neural network weights data in storage. + + Returns: + self.output.weights (HDFWeights, None): If defined, the weights in + storage. + """ + if 'weights' in self.output: + return self.output.weights + + return None + + @weights.setter + def weights(self, weights: HDFWeights) -> None: + """Set the weights of the atomic neural networks in storage. + + Args: + weights (HDFWeights): Atomic neural network weights wrapped in a + HDWeights storage container. + """ + self.output.weights = weights + + @property + def sfvalues(self) -> Optional[HDFSymmetryFunctionValues]: + """Show the symmetry function value data in storage. + + Returns: + self.output.sfvalues (HDFSymmetryFunctionValues, None): If defined, + the symmetry function values in storage. """ - Add a job to the training database. Currently only :class:`.TrainingContainer` are supported. + if 'sfvalues' in self.output: + return self.output.sfvalues + + return None + + @sfvalues.setter + def sfvalues(self, sfvalues: HDFSymmetryFunctionValues) -> None: + """Set the symmetry function value data in storage. Args: - job (:class:`.TrainingContainer`): job to add to database - """ - self._training_ids.append(job.id) - - def write_input(self): - with open(os.path.join(self.working_directory, "input.data"), "w") as f: - for id in self._training_ids: - container = self.project.load(id) - for atoms, energy, forces, _ in zip(*container.to_list()): - f.write("begin\n") - c = np.array(atoms.cell) * AngstromToBohr - f.write(f"lattice {c[0][0]:13.08f} {c[0][1]:13.08f} {c[0][2]:13.08f}\n") - f.write(f"lattice {c[1][0]:13.08f} {c[1][1]:13.08f} {c[1][2]:13.08f}\n") - f.write(f"lattice {c[2][0]:13.08f} {c[2][1]:13.08f} {c[2][2]:13.08f}\n") - p = atoms.positions * AngstromToBohr - ff = forces * ElectronVoltToHartree / AngstromToBohr - for i in range(len(atoms)): - f.write(f"atom {p[i, 0]:13.08f} {p[i, 1]:13.08f} {p[i, 2]:13.08f}") - f.write(f" {atoms.elements[i].Abbreviation} 0.0 0.0") - f.write(f" {ff[i, 0]:13.08f} {ff[i, 1]:13.08f} {ff[i, 2]:13.08f}\n") - f.write(f"energy {energy * ElectronVoltToHartree}\n") - f.write("charge 0.0\nend\n") - - with open(os.path.join(self.working_directory, "input.nn"), "w") as f: - f.write(input_template.format(element=self.input.element)) + sfvalues (HDFSymmetryFunctionValues): Symmetry function values + wrapped in a HDF storage container. + """ + self.output.sfvalues = sfvalues @property - def lammps_potential(self): + def splittraintest(self) -> Optional[HDFSplitTrainTest]: + """Show the split between training and testing data in storage. + + Returns: + self.output.splittraintest (HDFSplitTrainTest, None): If defined, + the splitting data in storage. + """ + if 'splittraintest' in self.output: + return self.output.splittraintest + + return None + + @splittraintest.setter + def splittraintest(self, splittraintest: HDFSplitTrainTest) -> None: + """Set the split between training and testing data in storage. + + Args: + splittraintest (HDFSplitTrainTest): Split between training and + testing data wrapped in a HDF storage container. """ - :class:`pandas.DataFrame`: dataframe compatible with :attribute:`.LammpsInteractive.potential` + self.output.splittraintest = splittraintest + + def _add_training_data(self, container: TrainingContainer) -> None: + """Add a set of training data to storage. + + Args: + container (TrainingContainer): The training data that will be added + to `self`. + """ + # Get a dictionary of all property arrays saved in this container. + arrays = container.to_dict() + arraynames = arrays.keys() + + # Iterate over the structures by zipping, i.e. transposing the + # dictionary values. + for properties in zip(*arrays.values()): + zipped = dict(zip(arraynames, properties)) + self.storage.input.training_data.add_structure(**zipped) + + def _get_training_data(self) -> TrainingStorage: + """Show the stored training data. + + Returns: + self.storage.input.training_data (TrainingContainer): The stored + training data. + """ + return self.storage.input.training_data + + def _get_predicted_data(self) -> FlattenedStorage: + """Show the predicted data after a successful fit. + + Returns: + predicted_data (TrainingContainer): The predicted data in storage. + At the moment, pyiron can interpret the energies and forces + predicted by RuNNer. + """ + # Energies and forces will only be available after RuNNer Mode 3. + if 'energy' not in self.output: + raise RuntimeError('You have to run RuNNer prediction mode ' + + '(Mode 3) before you can access predictions.') + + # Get a list of structures. + structures = list(self.training_data.iter_structures()) + + # Get the values of all properties RuNNer can predict for a structure. + pred_properties = {'energy': np.full((len(structures),), np.nan), + 'forces': np.full((3, len(structures)), np.nan)} + for prop in pred_properties: + if prop in self.output: + pred_properties[prop] = self.output[prop] + + predicted_data = FlattenedStorage() + + for structure, energy, force in zip(structures, + pred_properties['energy'], + pred_properties['forces']): + predicted_data.add_chunk(len(structure), energy=energy, + forces=force) + + return predicted_data + + def get_lammps_potential( + self, + elements: Optional[List[str]] = None + ) -> pd.DataFrame: + """Return a pandas dataframe with information for setting up LAMMPS. + + The nnp pair_style for LAMMPS is provided by the external package n2p2, + that is maintained by Andreas Singgraber. Please take a look at their + [documentation](https://compphysvienna.github.io/n2p2/interfaces/pair_\ + nnp.html) + to understand more about the configuration options. + + Args: + elements (List[str], optional): A list of elements for which the + potential will be returned. + + Returns: + df (pd.DataFrame): A dataframe containing all the information + required to set up a LAMMPS job with RuNNer. """ if not self.status.finished: - raise RuntimeError("Job must have successfully finished before potential files can be copied!") - weight_file = glob(f'{self.working_directory}/weights.*.data')[0] + raise RuntimeError('LAMMPS potential can only be generated after a ' + + 'successful fit.') + + if 'weights' not in self.output or 'scaling' not in self.output: + raise RuntimeError('This potential has not been trained yet.') + + # Get all elements in the training dataset. + if elements is None: + elements = self.training_data.get_elements() + + # Create a list of all files needed by the potential. + files = [f'{self.working_directory}/input.nn', + f'{self.working_directory}/scaling.data'] + + # Add the weight files. + for elem in elements: + atomic_number = atomic_numbers[elem] + filename = f'weights.{atomic_number:03}.data' + files.append(f'{self.working_directory}/{filename}') + + # Save the mapping of elements between LAMMPS and n2p2. + emap = ' '.join(el for el in elements) + + # Get the cutoff radius of the symmetry functions. + cutoffs = self.parameters.symfunction_short.cutoffs + cutoff = cutoffs[0] + + if len(cutoffs) > 1: + raise RuntimeError('LAMMPS potential can only be generated for a ' + + 'uniform cutoff radius.') + return pd.DataFrame({ - 'Name': ['RuNNer-Cu'], - 'Filename': [[f'{self.working_directory}/input.nn', - weight_file, - f'{self.working_directory}/scaling.data']], - 'Model': ['RuNNer'], - 'Species': [['Cu']], - 'Config': [['pair_style nnp dir "./" showew no showewsum 0 resetew no maxew 100 cflength 1.8897261328 cfenergy 0.0367493254 emap "1:Cu"\n', - 'pair_coeff * * 12\n']] - }) - - def collect_output(self): - pass - - # To link to the executable from the notebook - def _executable_activate(self, enforce=False): + 'Name': [f"RuNNer-{''.join(elements)}"], + 'Filename': [files], + 'Model': ['RuNNer'], + 'Species': [elements], + 'Config': [[f'pair_style hdnnp {cutoff * Bohr} dir "./" ' + + 'showew yes showewsum 0 resetew no maxew 100 ' + + 'cflength 1.8897261328 cfenergy 0.0367493254\n', + f'pair_coeff * * {emap}\n']] + }) + + def write_input(self) -> None: + """Write the relevant job input files. + + This routine writes the input files for the job using the ASE Runner + calculator. + """ + input_properties = {'sfvalues': None, 'splittraintest': None, + 'weights': None, 'scaling': None} + + for prop in input_properties: + if prop in self.output and self.output[prop] is not None: + input_properties[prop] = self.output[prop].to_runnerase() + + # Create an ASE Runner calculator object. + # Pay attention to the different name: `structures` --> `dataset`. + calc = Runner( + label='pyiron', + dataset=container_to_ase(self.training_data), + scaling=input_properties['scaling'], + weights=input_properties['weights'], + sfvalues=input_properties['sfvalues'], + splittraintest=input_properties['splittraintest'], + **self.parameters.to_builtin() + ) + + # Set the correct elements of the system. + calc.set_elements() + + # If no seed was specified yet, choose a random value. + if 'random_seed' not in calc.parameters: + calc.set(random_seed=np.random.randint(1, 1000)) + + # If no dataset was attached to the calculator, the single structure + # stored as the atoms property will be written instead. + atoms = calc.dataset or calc.atoms + + # Set the correct calculation directory and file prefix. + calc.directory = self.working_directory + calc.prefix = f'mode{self.parameters.runner_mode}' + + # Set the 'flags' which ASE uses to see which input files need to + # be written. + targets = {1: 'sfvalues', 2: 'fit', 3: 'energy'} + + calc.write_input( + atoms, + targets[self.parameters.runner_mode], + system_changes=None + ) + + def _executable_activate(self, enforce: bool = False) -> None: + """Link to the RuNNer executable.""" if self._executable is None or enforce: self._executable = Executable( - codename="runner", module="runner", path_binary_codes=state.settings.resource_paths + codename='runner', + module='runner', + path_binary_codes=state.settings.resource_paths ) -input_template = """### #################################################################################################################### -### This is the input file for the RuNNer tutorial (POTENTIALS WORKSHOP 2021-03-10) -### This input file is intended for release version 1.2 -### RuNNer is hosted at www.gitlab.com. The most recent version can only be found in this repository. -### For access please contact Prof. Jörg Behler, joerg.behler@uni-goettingen.de -### -### #################################################################################################################### -### General remarks: -### - commands can be switched off by using the # character at the BEGINNING of the line -### - the input file can be structured by blank lines and comment lines -### - the order of the keywords is arbitrary -### - if keywords are missing, default values will be used and written to runner.out -### - if mandatory keywords or keyword options are missing, RuNNer will stop with an error message -### -######################################################################################################################## -######################################################################################################################## -### The following keywords just represent a subset of the keywords offered by RuNNer -######################################################################################################################## -######################################################################################################################## - -######################################################################################################################## -### general keywords -######################################################################################################################## -nn_type_short 1 # 1=Behler-Parrinello -runner_mode 1 # 1=calculate symmetry functions, 2=fitting mode, 3=predicition mode -number_of_elements 1 # number of elements -elements {element} # specification of elements -random_seed 10 # integer seed for random number generator -random_number_type 6 # 6 recommended - -######################################################################################################################## -### NN structure of the short-range NN -######################################################################################################################## -use_short_nn # use NN for short range interactions -global_hidden_layers_short 2 # number of hidden layers -global_nodes_short 15 15 # number of nodes in hidden layers -global_activation_short t t l # activation functions (t = hyperbolic tangent, l = linear) - -######################################################################################################################## -### symmetry function generation ( mode 1): -######################################################################################################################## -test_fraction 0.10000 # threshold for splitting between fitting and test set - -######################################################################################################################## -### symmetry function definitions (all modes): -######################################################################################################################## -cutoff_type 1 -symfunction_short {element} 2 {element} 0.000000 0.000000 12.000000 -symfunction_short {element} 2 {element} 0.006000 0.000000 12.000000 -symfunction_short {element} 2 {element} 0.016000 0.000000 12.000000 -symfunction_short {element} 2 {element} 0.040000 0.000000 12.000000 -symfunction_short {element} 2 {element} 0.109000 0.000000 12.000000 - -symfunction_short {element} 3 {element} {element} 0.00000 1.000000 1.000000 12.000000 -symfunction_short {element} 3 {element} {element} 0.00000 1.000000 2.000000 12.000000 -symfunction_short {element} 3 {element} {element} 0.00000 1.000000 4.000000 12.000000 -symfunction_short {element} 3 {element} {element} 0.00000 1.000000 16.000000 12.000000 -symfunction_short {element} 3 {element} {element} 0.00000 -1.000000 1.000000 12.000000 -symfunction_short {element} 3 {element} {element} 0.00000 -1.000000 2.000000 12.000000 -symfunction_short {element} 3 {element} {element} 0.00000 -1.000000 4.000000 12.000000 -symfunction_short {element} 3 {element} {element} 0.00000 -1.000000 16.000000 12.000000 - -######################################################################################################################## -### fitting (mode 2):general inputs for short range AND electrostatic part: -######################################################################################################################## -epochs 20 # number of epochs -fitting_unit eV # unit for error output in mode 2 (eV or Ha) -precondition_weights # optional precondition initial weights - -######################################################################################################################## -### fitting options ( mode 2): short range part only: -######################################################################################################################## -short_energy_error_threshold 0.10000 # threshold of adaptive Kalman filter short E -short_force_error_threshold 1.00000 # threshold of adaptive Kalman filter short F -kalman_lambda_short 0.98000 # Kalman parameter short E/F, do not change -kalman_nue_short 0.99870 # Kalman parameter short E/F, do not change -use_short_forces # use forces for fitting -repeated_energy_update # optional: repeat energy update for each force update -mix_all_points # do not change -scale_symmetry_functions # optional -center_symmetry_functions # optional -short_force_fraction 0.01 # -force_update_scaling -1.0 # - -######################################################################################################################## -### output options for mode 2 (fitting): -######################################################################################################################## -write_trainpoints # write trainpoints.out and testpoints.out files -write_trainforces # write trainforces.out and testforces.out files - -######################################################################################################################## -### output options for mode 3 (prediction): -######################################################################################################################## -calculate_forces # calculate forces -calculate_stress # calculate stress tensor -""" + def collect_output(self) -> None: + """Read and store the job results.""" + # Compose job label (needed by the ASE I/O routines) and store dir. + label = f'{self.working_directory}/mode{self.parameters.runner_mode}' + directory = self.working_directory + + # If successful, RuNNer Mode 1 returns symmetry function values for + # each structure and the information, which structure belongs to the + # training and which to the testing set. + if self.parameters.runner_mode == 1: + results = read_results_mode1(label, directory) + + # Transform sfvalues into the pyiron class for HDF5 storage and + # store it in the output dictionary. + sfvalues = HDFSymmetryFunctionValues() + sfvalues.from_runnerase(results['sfvalues']) + self.output.sfvalues = sfvalues + + # Transform split data between training and testing set into the + # pyiron class for HDF5 storage and store it in the output + # dictionary. + splittraintest = HDFSplitTrainTest() + splittraintest.from_runnerase(results['splittraintest']) + self.output.splittraintest = splittraintest + + # If successful, RuNNer Mode 2 returns the weights of the atomic neural + # networks, the symmetry function scaling data, and the results of the + # fitting process. + elif self.parameters.runner_mode == 2: + results = read_results_mode2(label, directory) + + fitresults = HDFFitResults() + fitresults.from_runnerase(results['fitresults']) + self.output.fitresults = fitresults + + weights = HDFWeights() + weights.from_runnerase(results['weights']) + self.output.weights = weights + + scaling = HDFScaling() + scaling.from_runnerase(results['scaling']) + self.output.scaling = scaling + + # If successful, RuNNer Mode 3 returns the energy and forces of the + # structure for which it was executed. + elif self.parameters.runner_mode == 3: + results = read_results_mode3(directory) + self.output.energy = results['energy'] + self.output.forces = results['forces'] + + # Store all calculation results in the project's HDF5 file. + self.to_hdf() + + def to_hdf( + self, + hdf: Optional[ProjectHDFio] = None, + group_name: Optional[str] = None + ) -> None: + """Store all job information in HDF5 format. + + Args: + hdf (ProjectHDFio): HDF5-object which contains the project data. + group_name (str): Subcontainer name. + """ + # Replace the runnerase class `SymmetryFunctionSet` by the extended + # class from the `storageclasses` module which knows how to write itself + # to hdf. + sfset = self.parameters.pop('symfunction_short') + new_sfset = HDFSymmetryFunctionSet() + new_sfset.from_runnerase(sfset) + self.parameters.symfunction_short = new_sfset + + GenericJob.to_hdf(self, hdf=hdf, group_name=group_name) + HasStorage.to_hdf(self, hdf=self.project_hdf5, group_name='') + + def from_hdf( + self, + hdf: Optional[ProjectHDFio] = None, + group_name: Optional[str] = None + ) -> None: + """Reload all job information from HDF5 format. + + Args: + hdf (ProjectHDFio): HDF5-object which contains the project data. + group_name (str): Subcontainer name. + """ + GenericJob.from_hdf(self, hdf=hdf, group_name=group_name) + HasStorage.from_hdf(self, hdf=self.project_hdf5, group_name='') diff --git a/pyiron_contrib/atomistics/runner/storageclasses.py b/pyiron_contrib/atomistics/runner/storageclasses.py new file mode 100644 index 000000000..5daaff5fc --- /dev/null +++ b/pyiron_contrib/atomistics/runner/storageclasses.py @@ -0,0 +1,356 @@ +# coding: utf-8 +# Copyright (c) Georg-August-Universität Göttingen - Behler Group +# Distributed under the terms of "New BSD License", see the LICENSE file. +"""Class Overrides for all `runnerase` storage classes. + +The ASE calculator for RuNNer, provided by the Python package `runnerase`, +stores data like the values of symmetry functions or the weights of atomic +neural networks in custom classes. However, these classes generally do not know +how to write themselves to HDF5 storage format. Therefore, these classes are +extended in this module with additional functions, typically `to_hdf(...)` and +`from_hdf(...)`. + +Attributes: + HDFSymmetryFunctionValues (FlattenedStorage): Storage container for + symmetry function values. + RunneraseHDFMixin (HasHDF): Abstract mixin for all classes that store + RuNNer results to HDF. + HDFSymmetryFunctionSet (SymmetryFunctionSet, RunneraseHDFMixin): Storage + container for a set of symmetry functions. + HDFSplitTrainTest (RunnerSplitTrainTest, RunneraseHDFMixin): Storage + container for the splitting between training and testing dataset. + HDFFitResults (RunnerFitResults, RunneraseHDFMixin): Storage container + for the results of a RuNNer fit. + HDFWeights (RunnerWeights, RunneraseHDFMixin): Storage container for the + weights of atomic neural networks. + HDFScaling (RunnerScaling, RunneraseHDFMixin): Storage container for the + symmetry function scaling data. + +.. _RuNNer online documentation: + https://theochem.gitlab.io/runner +""" + +from typing import Optional, Union +from abc import abstractmethod + +import numpy as np + +from runnerase.symmetryfunctions import SymmetryFunctionSet +from runnerase.storageclasses import (RunnerSymmetryFunctionValues, + RunnerStructureSymmetryFunctionValues, + RunnerSplitTrainTest, + RunnerFitResults, + RunnerWeights, RunnerScaling) +from pyiron_base import ProjectHDFio +from pyiron_base import HasHDF +from pyiron_base import FlattenedStorage + +from .utils import pad, unpad + + +class HDFSymmetryFunctionValues(FlattenedStorage): + """Extend runnerase RunnerSymmetryFunctionValues with HDF5 compatibility.""" + + __hdf_version__ = '0.3.0' + + def from_runnerase( + self, + runnerase_sfvalues: RunnerSymmetryFunctionValues + ) -> None: + """Fill `self` with information of the corresponding `runnerase` object. + + `runnerase` stores the symmetry function values of each structure in + a separate `RunnerStructureSymmetryFunctionValues` object. However, + it is very inefficient to read and write all of this information + separately into HDF5 storage. Therefore, we use a `FlattenedStorage` + where all the symmetry function values of all structures are stored + in one large flat array. + + Matters are complicated further because `FlattenedStorage` only accepts + data chunks (read here: structures) where all different arrays of + information have the same length. Unfortunately, one can have different + numbers of symmetry functions for each element and varying numbers of + elements for each structure in a training dataset. + + The solution here is to `pad` the sfvalues of each group of elements in + one structure with rows filled with `np.NaN`. This way, all arrays for + one structure have length `number_of_atoms` and can be efficiently + stored and retrieved even for large datasets. + + Parameters + ---------- + runnerase_sfvalues : RunnerSymmetryFunctionValues + A `runnerase` class object containing the symmetry function values + for a whole dataset. + """ + for structure_sfvalues in runnerase_sfvalues.data: + + # Preprocess the symmetry function value arrays. + sfvalues_arrays = {} + for element, sfvalues in structure_sfvalues.data.items(): + name = f'sfvalues_{element}' + shape = (sfvalues.shape[1],) + # Add arrays for storing the symmetry function values of all + # atoms of the same `element`, unless they are already present. + if name not in self.list_arrays(): + self.add_array(name, shape=shape, dtype=np.float64, + per='element', fill=np.NaN) + + # Pad the sfvalues of the current `element` with `np.NaN` rows, + # so that the total length is equal to the number of atoms in + # the structure. + sfvalues_padded = pad(sfvalues, len(structure_sfvalues)) + + sfvalues_arrays[name] = sfvalues_padded + + self.add_chunk( + len(structure_sfvalues), + energy_total=structure_sfvalues.energy_total, + energy_short=structure_sfvalues.energy_short, + energy_elec=structure_sfvalues.energy_elec, + charge=structure_sfvalues.charge, + **sfvalues_arrays + ) + + def to_runnerase(self) -> RunnerSymmetryFunctionValues: + """Create the corresponding `runnerase` object from `self`. + + Returns + ---------- + runnerase_sfvalues : RunnerSymmetryFunctionValues + A `runnerase` class object containing the symmetry function values + for a whole dataset. + """ + runnerase_sfvalues = RunnerSymmetryFunctionValues() + + for chunk_idx in range(len(self)): + # Create a new object for storing the sfvalues of one structure. + struct_sfvalues = RunnerStructureSymmetryFunctionValues() + + # Fill the object with per-chunk properties. + struct_sfvalues.energy_total = self['energy_total', chunk_idx] + struct_sfvalues.energy_short = self['energy_short', chunk_idx] + struct_sfvalues.energy_elec = self['energy_elec', chunk_idx] + struct_sfvalues.charge = self['charge', chunk_idx] + + # Read the symmetry function values. + for arrayname in self.list_arrays(): + if arrayname.startswith('sfvalues'): + element = arrayname.split('_')[1] + sfvalues = self[f'sfvalues_{element}', chunk_idx] + struct_sfvalues.data[element] = unpad(sfvalues) + + # Append the structure to the container object + # `RunnerSymmetryFunctionValues`. + runnerase_sfvalues.data.append(struct_sfvalues) + + return runnerase_sfvalues + + +class RunneraseHDFMixin(HasHDF): + """Abstract Mixin to add HDF5 compatibility to runnerase classes.""" + + __hdf_version__ = '0.3.0' + + @property + @abstractmethod + def runnerase_properties(self): + """Define the class properties stored in `self.baseclass` objects.""" + ... + + @property + @abstractmethod + def baseclass(self): + """Define the runnerase class which is wrapped by this HDF class.""" + ... + + def from_runnerase( + self, + runnerase_class: Union[RunnerSplitTrainTest, RunnerWeights, + RunnerScaling, RunnerFitResults, + RunnerSymmetryFunctionValues] + ) -> None: + """Fill `self` with information of the corresponding `runnerase` object. + + Args: + runnerase_class (runnerase storage class): The runnerase class whose + information will be wrapped. + """ + for prop in self.runnerase_properties: + self.__dict__[prop] = runnerase_class.__dict__[prop] + + def to_runnerase(self) -> Union[RunnerSplitTrainTest, RunnerWeights, + RunnerScaling, RunnerFitResults, + RunnerSymmetryFunctionValues]: + """Create the corresponding `runnerase` object from `self`. + + Returns: + runnerase_class (runnerase storage class): The runnerase class whose + information was wrapped. + """ + runnerase_class = self.baseclass() + + for prop in self.runnerase_properties: + runnerase_class.__dict__[prop] = self.__dict__[prop] + + return runnerase_class + + def _to_hdf(self, hdf: ProjectHDFio) -> None: + """Write `self` to HDF5 storage. + + Args: + hdf (ProjectHDFio): The HDF file where `self` will be stored. + """ + for prop in self.runnerase_properties: + hdf[f'{prop}'] = self.__dict__[prop] + + def _from_hdf( + self, + hdf: ProjectHDFio, + version: Optional[str] = None + ) -> Union[RunnerSplitTrainTest, RunnerWeights, RunnerScaling, + RunnerFitResults, RunnerSymmetryFunctionValues]: + """Read `self` from HDF5 storage. + + Args: + hdf (ProjectHDFio): The HDF file where `self` will be stored. + version (str): The HDF version of the storage file. + """ + if version != self.__hdf_version__: + raise RuntimeError('Invalid HDF5 version found while reading ' + + self.__class__.__name__) + + # Open HDF file at the right group with a context manager. + for node in hdf.list_nodes(): + for prop in self.runnerase_properties: + if prop in node: + self.__dict__[prop] = hdf[node] + + return self + + def _get_hdf_group_name(self): + """Get the name of the group where this object is stored in HDF.""" + return self.__class__.__name__ + + +class HDFSymmetryFunctionSet(SymmetryFunctionSet, RunneraseHDFMixin): + """Extend runnerase SymmetryFunctionSet with HDF5 compatibility.""" + + __hdf_version__ = '0.3.0' + + @property + def runnerase_properties(self): + """Show class properties stored in `SymmetryFunctionSet` objects.""" + return ['_sets', '_symmetryfunctions', 'min_distances'] + + @property + def baseclass(self): + """Define the base class which is wrapped by this HDF class.""" + return SymmetryFunctionSet + + def _to_hdf( + self, + hdf: ProjectHDFio + ) -> None: + """Write `self` to HDF5 storage. + + `runnerase`s SymmetryFunctionSet has the convenient property that + all symmetry functions can be written to and read from a list + representation. Therefore, they are also stored as lists in HDF format. + + Args: + hdf (ProjectHDFio): The HDF file where `self` will be stored. + """ + for idx, sfset in enumerate(self.sets): + hdfset = HDFSymmetryFunctionSet() + hdfset.from_runnerase(sfset) + hdfset.to_hdf(hdf=hdf, group_name=f'set__index_{idx}') + + symmetryfunctions = [sf.to_list() for sf in self.symmetryfunctions] + hdf['symmetryfunctions__index_0'] = symmetryfunctions + + def _from_hdf( + self, + hdf: ProjectHDFio, + version: Optional[str] = None + ) -> 'HDFSplitTrainTest': + """Read `self` from HDF5 storage. + + Args: + hdf (ProjectHDFio): The HDF file where `self` will be stored. + version (str): The HDF version of the storage file. + """ + if version != self.__hdf_version__: + raise RuntimeError('Invalid HDF5 version found while reading ' + + self.__class__.__name__) + + # Reload symmetry function sets. + for group in hdf.list_groups(): + if group.startswith('set'): + new_set = hdf.__getitem__(group).to_object() + self.append(new_set) + + for node in hdf.list_nodes(): + # Reload symmetry functions. + if node.startswith('symmetryfunctions'): + sflist = hdf.__getitem__(node) + self.from_list(sflist) + + return self + + +class HDFSplitTrainTest(RunnerSplitTrainTest, RunneraseHDFMixin): + """Mix HDF5 compatibility into RunnerSplitTrainTest`.""" + + @property + def runnerase_properties(self): + """Show class properties stored in `RunnerSplitTrainTest` objects.""" + return ['train', 'test'] + + @property + def baseclass(self): + """Define the base class which is wrapped by this HDF class.""" + return RunnerSplitTrainTest + + +class HDFFitResults(RunnerFitResults, RunneraseHDFMixin): + """Mix HDF5 compatibility into RunnerFitResults`.""" + + @property + def runnerase_properties(self): + """Show class properties stored in `RunnerFitResults` objects.""" + return ['epochs', 'rmse_energy', 'rmse_forces', 'rmse_charge', + 'opt_rmse_epoch', 'units'] + + @property + def baseclass(self): + """Define the base class which is wrapped by this HDF class.""" + return RunnerFitResults + + +class HDFWeights(RunnerWeights, RunneraseHDFMixin): + """Mix HDF5 compatibility into RunnerWeights`.""" + + @property + def runnerase_properties(self): + """Show class properties stored in `RunnerWeights` objects.""" + return ['data'] + + @property + def baseclass(self): + """Define the base class which is wrapped by this HDF class.""" + return RunnerWeights + + +class HDFScaling(RunnerScaling, RunneraseHDFMixin): + """Mix HDF5 compatibility into RunnerScaling`.""" + + @property + def runnerase_properties(self): + """Show class properties stored in `RunnerScaling` objects.""" + return ['data', 'target_min', 'target_max'] + + @property + def baseclass(self): + """Define the base class which is wrapped by this HDF class.""" + return RunnerScaling diff --git a/pyiron_contrib/atomistics/runner/utils.py b/pyiron_contrib/atomistics/runner/utils.py new file mode 100644 index 000000000..b9e465351 --- /dev/null +++ b/pyiron_contrib/atomistics/runner/utils.py @@ -0,0 +1,143 @@ +# encoding: utf-8 +# Copyright (c) Georg-August-Universität Göttingen - Behler Group +# Distributed under the terms of "New BSD License", see the LICENSE file. +"""Utility functions for use with the pyiron Hamiltonian of RuNNer. + +.. _RuNNer online documentation: + https://theochem.gitlab.io/runner +""" + +from typing import List + +import numpy as np + +from ase.atoms import Atoms + +from pyiron_atomistics.atomistics.structure.atoms import pyiron_to_ase + +from runnerase.singlepoint import RunnerSinglePointCalculator + +from ..atomistics.job.trainingcontainer import TrainingContainer + + +def container_to_ase(container: TrainingContainer) -> List[Atoms]: + """Convert a `TrainingContainer` into a list of ASE Atoms objects. + + Args: + container (TrainingContainer): The training data to be converted. + + Returns: + structures (List[Atoms]): A list of ASE Atoms objects. + """ + structure_lst = [] + + arrays = container.to_dict() + arraynames = arrays.keys() + + # Iterate over the structures by zipping the dictionary values. + for properties in zip(*arrays.values()): + zipped = dict(zip(arraynames, properties)) + + # Retrieve atomic positions, cell vectors, etc. + atoms = pyiron_to_ase(zipped['structure']) + + # Attach charges to the Atoms object. + if 'charges' in zipped: + atoms.set_initial_charges(zipped['charges']) + + # Store all properties that will be saved on the calculator below. + calc_properties = {'energy': None, 'forces': None, 'totalcharge': None} + for prop in calc_properties: + if prop in zipped: + calc_properties[prop] = zipped[prop] + + # Overwrite the totalcharge if the property was not present. + if calc_properties['totalcharge'] is None: + totalcharge = np.sum(atoms.get_initial_charges()) + calc_properties['totalcharge'] = totalcharge + + # Storage energies, forces, and totalcharge on a calculator object. + atoms.calc = RunnerSinglePointCalculator( + atoms=atoms, + energy=calc_properties['energy'], + forces=calc_properties['forces'], + totalcharge=calc_properties['totalcharge'] + ) + + structure_lst.append(atoms) + + return structure_lst + + +def ase_to_container( + structures: List[Atoms], + container: TrainingContainer +) -> None: + """Append `structures` to `TrainingContainer`. + + Args: + structures (List[Atoms]): A list of ASE Atoms objects to be stored on + the given `container`. + container (TrainingContainer): The container to which the data will be + appended. + """ + for structure in structures: + + properties = {} + + # If the structure has a calculator attached to it, get energies, + # forces, etc. + if structure.calc is not None: + properties['energy'] = structure.get_potential_energy() + properties['forces'] = structure.get_forces() + properties['charges'] = structure.get_initial_charges() + + if isinstance(structure.calc, RunnerSinglePointCalculator): + properties['totalcharge'] = structure.calc.get_property( + 'totalcharge' + ) + + else: + properties['totalcharge'] = np.sum(properties['charges']) + + # StructureStorage needs a `spins` property, but not all ASE Atoms + # objects have that. + if not hasattr(structure, 'spins'): + structure.spins = None + + container.include_structure(structure, **properties) + + +def pad(array: np.ndarray, desired_length: int) -> np.ndarray: + """Pad `array` with `np.NaN` rows up to `desired_length`. + + This routine pads an array of symmetry function values with np.NaN in those + places where the index (first column of sfvalue arrays) is missing. + + Args: + array (np.ndarray): The array to be padded. The first column must + contain a continuous index. + desired_length (int): The final desired length of the array. + + Returns: + array_padded (np.ndarray): The padded array. + """ + # Create a sequence of missing indices. + all_indices = np.arange(0, desired_length, 1) + contained_indices = array[:, 0].astype(int) + missing_indices = np.delete(all_indices, contained_indices) + + # Create an np.NaN-filled array for the padded sfvalues data. + array_padded = np.empty(shape=(desired_length, array.shape[1])) + array_padded[:] = np.NaN + + # Insert first the data for this atom, second the np.NaN values. + array_padded[:len(contained_indices), :] = array + array_padded[len(contained_indices):, 0] = missing_indices + + return array_padded + + +def unpad(array: np.ndarray): + """Remove all rows containing NaN values from an ndarray.""" + return array[~np.isnan(array).any(axis=1), :] diff --git a/pyiron_contrib/generic/s3io.py b/pyiron_contrib/generic/s3io.py index 973e1bf54..35d77e69a 100644 --- a/pyiron_contrib/generic/s3io.py +++ b/pyiron_contrib/generic/s3io.py @@ -8,8 +8,8 @@ import boto3 from botocore.client import Config -from pyiron_base.interfaces.has_groups import HasGroups -from pyiron_base.generic.filedata import load_file, FileDataTemplate +from pyiron_base import HasGroups +from pyiron_base import load_file, FileDataTemplate class S3FileData(FileDataTemplate): diff --git a/setup.py b/setup.py index 120136fc4..9409cb2d5 100644 --- a/setup.py +++ b/setup.py @@ -31,27 +31,27 @@ keywords='pyiron', packages=find_packages(exclude=["*tests*"]), install_requires=[ - 'matplotlib==3.5.1', - 'numpy==1.22.2', - 'pyiron_base==0.5.5', - 'scipy==1.8.0', - 'seaborn==0.11.2', - 'pyparsing==3.0.7' + 'matplotlib==3.6.1', + 'numpy==1.23.4', + 'pyiron_base==0.5.26', + 'scipy==1.9.3', + 'seaborn==0.12.0', + 'pyparsing==3.0.9' ], extras_require={ 'atomistic': [ 'ase==3.22.1', - 'pyiron_atomistics==0.2.37', + 'pyiron_atomistics==0.2.58', 'pycp2k==0.2.2', ], 'fenics': [ 'fenics==2019.1.0', 'mshr==2019.1.0', ], - 'image': ['scikit-image==0.19.2'], + 'image': ['scikit-image==0.19.3'], 'generic': [ - 'boto3==1.21.3', - 'moto==3.0.4' + 'boto3==1.24.96', + 'moto==4.0.8' ], }, cmdclass=versioneer.get_cmdclass(), diff --git a/tests/RDM/test_storagejob.py b/tests/RDM/test_storagejob.py index ef98ab19d..4252a616c 100644 --- a/tests/RDM/test_storagejob.py +++ b/tests/RDM/test_storagejob.py @@ -8,7 +8,7 @@ from pyiron_contrib.RDM.storagejob import StorageJob from pyiron_contrib.generic.s3io import FileS3IO from pyiron_base._tests import TestWithCleanProject -from pyiron_base.generic.filedata import FileDataTemplate +from pyiron_base import FileDataTemplate full_bucket = "full_bucket" io_bucket = "io_bucket"