From 16e824ef9805d1320bf249464e22940e1d30da13 Mon Sep 17 00:00:00 2001 From: sudarsan1989 Date: Mon, 26 Jul 2021 22:35:38 +0200 Subject: [PATCH 01/12] Implement a decorator class to handle unit conversions --- pyiron_atomistics/lammps/units.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/pyiron_atomistics/lammps/units.py b/pyiron_atomistics/lammps/units.py index 2d9bb0f3b..2709a1403 100644 --- a/pyiron_atomistics/lammps/units.py +++ b/pyiron_atomistics/lammps/units.py @@ -5,6 +5,7 @@ import numpy as np import scipy.constants as spc import warnings +import functools __author__ = "Joerg Neugebauer, Sudarsan Surendralal, Jan Janssen" __copyright__ = ( @@ -204,7 +205,7 @@ def convert_array_to_pyiron_units(self, array, label): label (str): The label of the quantity (must be a key in the dictionary `quantity_dict`) Returns: - numpy.ndarray: The array after conversion + ndarray: The array after conversion """ if label in quantity_dict.keys(): @@ -213,3 +214,23 @@ def convert_array_to_pyiron_units(self, array, label): warnings.warn(message="Warning: Couldn't determine the LAMMPS to pyiron unit conversion type of quantity " "{}. Returning un-normalized quantity".format(label)) return array + + +class UnitsDecorator: + + def __init__(self): + self._units = None + self._label = None + + def __call__(self, label, units=None): + if units is not None: + self._units = units + self._label = label + return self.__decorate_to_pyiron + + def __decorate_to_pyiron(self, function): + @functools.wraps(function) + def dec(*args, **kwargs): + return UnitConverter(self._units).convert_array_to_pyiron_units(function(*args, **kwargs), + label=self._label) + return dec From 198c72e2a686f06c6ed9f05b280ff52247d67380 Mon Sep 17 00:00:00 2001 From: sudarsan1989 Date: Mon, 26 Jul 2021 22:37:12 +0200 Subject: [PATCH 02/12] No non-keyword args allowed --- pyiron_atomistics/lammps/units.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyiron_atomistics/lammps/units.py b/pyiron_atomistics/lammps/units.py index 2709a1403..f80db4bcb 100644 --- a/pyiron_atomistics/lammps/units.py +++ b/pyiron_atomistics/lammps/units.py @@ -222,9 +222,7 @@ def __init__(self): self._units = None self._label = None - def __call__(self, label, units=None): - if units is not None: - self._units = units + def __call__(self, units, label): self._label = label return self.__decorate_to_pyiron From 4849bdb5293fca932957f221de39a449c4223480 Mon Sep 17 00:00:00 2001 From: sudarsan1989 Date: Wed, 28 Jul 2021 10:39:28 +0200 Subject: [PATCH 03/12] Introduce attribute `units` --- pyiron_atomistics/lammps/base.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/pyiron_atomistics/lammps/base.py b/pyiron_atomistics/lammps/base.py index 09556cb84..6e6c12368 100644 --- a/pyiron_atomistics/lammps/base.py +++ b/pyiron_atomistics/lammps/base.py @@ -61,6 +61,28 @@ def __init__(self, project, job_name): self._prism = None s.publication_add(self.publication) + @property + def units(self): + """ + Type of LAMMPS units used in the calculations. Can be either of 'metal', 'real', 'si', 'cgs', and 'lj' + + Returns: + str: Type of LAMMPS unit + """ + if self.input.control["units"] is not None: + return self.input.control["units"] + else: + # Default to metal units + return "metal" + + @units.setter + def units(self, val): + allowed_types = ['metal', 'real', 'si', 'cgs', 'lj'] + if val in allowed_types: + self.input.control["units"] = val + else: + raise ValueError("'{}' is not a valid LAMMPS unit") + @property def bond_dict(self): """ From da56a02d48636eae5cf3bb9fb00f546282ead503 Mon Sep 17 00:00:00 2001 From: sudarsan1989 Date: Wed, 28 Jul 2021 10:40:29 +0200 Subject: [PATCH 04/12] Use new attribute --- pyiron_atomistics/lammps/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyiron_atomistics/lammps/base.py b/pyiron_atomistics/lammps/base.py index 6e6c12368..09928be02 100644 --- a/pyiron_atomistics/lammps/base.py +++ b/pyiron_atomistics/lammps/base.py @@ -471,7 +471,7 @@ def collect_h5md_file(self, file_name="dump.h5", cwd=None): Returns: """ - uc = UnitConverter(self.input.control["units"]) + uc = UnitConverter(self.units) prism = UnfoldingPrism(self.structure.cell, digits=15) if np.matrix.trace(prism.R) != 3: raise RuntimeError("The Lammps output will not be mapped back to pyiron correctly.") @@ -559,7 +559,7 @@ def collect_output_log(self, file_name="log.lammps", cwd=None): Returns: """ - uc = UnitConverter(self.input.control["units"]) + uc = UnitConverter(self.units) self.collect_errors(file_name=file_name, cwd=cwd) file_name = self.job_file_name(file_name=file_name, cwd=cwd) if os.path.exists(file_name): @@ -927,7 +927,7 @@ def collect_dump_file(self, file_name="dump.out", cwd=None): Returns: """ - uc = UnitConverter(self.input.control["units"]) + uc = UnitConverter(self.units) file_name = self.job_file_name(file_name=file_name, cwd=cwd) if os.path.exists(file_name): output = {} From 4687c46f5112f89a8673f97e01034da23e7b7921 Mon Sep 17 00:00:00 2001 From: sudarsan1989 Date: Wed, 28 Jul 2021 12:05:55 +0200 Subject: [PATCH 05/12] Use converter for interactive jobs --- pyiron_atomistics/lammps/interactive.py | 29 ++++++++++++++++++------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/pyiron_atomistics/lammps/interactive.py b/pyiron_atomistics/lammps/interactive.py index 0cf37c2de..8a9c1850a 100644 --- a/pyiron_atomistics/lammps/interactive.py +++ b/pyiron_atomistics/lammps/interactive.py @@ -20,6 +20,7 @@ from pylammpsmpi import LammpsLibrary except ImportError: pass +from pyiron_atomistics.lammps.units import UnitConverter __author__ = "Osamu Waseda, Jan Janssen" __copyright__ = ( @@ -56,12 +57,14 @@ def _interactive_lib_command(self, command): self._interactive_library.command(command) def interactive_positions_getter(self): + uc = UnitConverter(units=self.units) positions = np.reshape( np.array(self._interactive_library.gather_atoms("x", 1, 3)), (len(self.structure), 3), ) if np.matrix.trace(self._prism.R) != 3: positions = np.matmul(positions, self._prism.R.T) + positions = uc.convert_array_to_pyiron_units(positions, label="positions") return positions.tolist() def interactive_positions_setter(self, positions): @@ -78,6 +81,7 @@ def interactive_positions_setter(self, positions): self._interactive_lib_command("change_box all remap") def interactive_cells_getter(self): + uc = UnitConverter(units=self.units) cc = np.array( [ [self._interactive_library.get_thermo("lx"), 0, 0], @@ -93,7 +97,7 @@ def interactive_cells_getter(self): ], ] ) - return self._prism.unfold_cell(cc) + return uc.convert_array_to_pyiron_units(self._prism.unfold_cell(cc), label="cells") def interactive_cells_setter(self, cell): self._prism = UnfoldingPrism(cell) @@ -132,15 +136,18 @@ def interactive_cells_setter(self, cell): ) def interactive_volume_getter(self): - return self._interactive_library.get_thermo("vol") + uc = UnitConverter(units=self.units) + return uc.convert_array_to_pyiron_units(self._interactive_library.get_thermo("vol"), label="volume") def interactive_forces_getter(self): + uc = UnitConverter(units=self.units) ff = np.reshape( np.array(self._interactive_library.gather_atoms("f", 1, 3)), (len(self.structure), 3), ) if np.matrix.trace(self._prism.R) != 3: ff = np.matmul(ff, self._prism.R.T) + ff = uc.convert_array_to_pyiron_units(ff, label="forces") return ff.tolist() def interactive_execute(self): @@ -534,8 +541,9 @@ def update_potential(self): self._interactive_lib_command(self.potential.Config[0][1]) def interactive_indices_getter(self): + uc = UnitConverter(units=self.units) lammps_indices = np.array(self._interactive_library.gather_atoms("type", 0, 1)) - indices = self.remap_indices(lammps_indices) + indices = uc.convert_array_to_pyiron_units(self.remap_indices(lammps_indices), label="indices") return indices.tolist() def interactive_indices_setter(self, indices): @@ -559,16 +567,20 @@ def interactive_indices_setter(self, indices): self._interactive_library.scatter_atoms("type", elem_all) def interactive_energy_pot_getter(self): - return self._interactive_library.get_thermo("pe") + uc = UnitConverter(units=self.units) + return uc.convert_array_to_pyiron_units(self._interactive_library.get_thermo("pe"), label="energy_pot") def interactive_energy_tot_getter(self): - return self._interactive_library.get_thermo("etotal") + uc = UnitConverter(units=self.units) + return uc.convert_array_to_pyiron_units(self._interactive_library.get_thermo("etotal"), label="energy_tot") def interactive_steps_getter(self): - return self._interactive_library.get_thermo("step") + uc = UnitConverter(units=self.units) + return uc.convert_array_to_pyiron_units(self._interactive_library.get_thermo("step"), label="steps") def interactive_temperatures_getter(self): - return self._interactive_library.get_thermo("temp") + uc = UnitConverter(units=self.units) + return uc.convert_array_to_pyiron_units(self._interactive_library.get_thermo("temp"), label="temperature") def interactive_stress_getter(self): """ @@ -595,6 +607,7 @@ def interactive_stress_getter(self): return ss def interactive_pressures_getter(self): + uc = UnitConverter(units=self.units) pp = np.array( [ [ @@ -617,7 +630,7 @@ def interactive_pressures_getter(self): rotation_matrix = self._prism.R.T if np.matrix.trace(rotation_matrix) != 3: pp = rotation_matrix.T @ pp @ rotation_matrix - return pp / 10000 # bar -> GPa + return uc.convert_array_to_pyiron_units(pp, label="pressure") def interactive_close(self): if self.interactive_is_activated(): From 26ce8f2e0e0ca942ab96155a479addaa38dba06d Mon Sep 17 00:00:00 2001 From: sudarsan1989 Date: Wed, 28 Jul 2021 12:41:55 +0200 Subject: [PATCH 06/12] Update tests --- tests/lammps/test_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lammps/test_base.py b/tests/lammps/test_base.py index fed745092..85798a140 100644 --- a/tests/lammps/test_base.py +++ b/tests/lammps/test_base.py @@ -332,7 +332,6 @@ def test_dump_parser_water(self): self.assertAlmostEqual(self.job_water_dump["output/generic/pressures"][-2][0, 0], 515832.570508186 / uc.pyiron_to_lammps("pressure"), 2) - self.job_water_dump.write_traj(filename="test.xyz", file_format="xyz") atom_indices = self.job_water_dump.structure.select_index("H") @@ -723,5 +722,6 @@ def test_potential_check(self): potential['Species'][0][0] = 'Al' self.job.potential = potential # shouldn't raise ValueError + if __name__ == "__main__": unittest.main() From e241ec32dad099a2968042f118b015c6a9f07656 Mon Sep 17 00:00:00 2001 From: sudarsan1989 Date: Wed, 28 Jul 2021 12:43:23 +0200 Subject: [PATCH 07/12] Add integration test! --- notebooks/integration/interactive_units.ipynb | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 notebooks/integration/interactive_units.ipynb diff --git a/notebooks/integration/interactive_units.ipynb b/notebooks/integration/interactive_units.ipynb new file mode 100644 index 000000000..bdf811460 --- /dev/null +++ b/notebooks/integration/interactive_units.ipynb @@ -0,0 +1,161 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "adb8a78b-112c-4d6f-8a50-aba827f48a45", + "metadata": {}, + "outputs": [], + "source": [ + "from pyiron import Project\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "63521ebb-b6fd-4ff0-ba5e-67d1bff34222", + "metadata": {}, + "outputs": [], + "source": [ + "pr = Project(\"water_interactive\")\n", + "pr.remove_jobs_silently()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "47c6a732-f0b9-4b92-b409-fb824d8fe2f5", + "metadata": {}, + "outputs": [], + "source": [ + "dx = 0.7\n", + "cell = np.eye(3) * 10\n", + "r_O = [0, 0, 0]\n", + "r_H1 = [dx, dx, 0]\n", + "r_H2 = [-dx, dx, 0]\n", + "water = pr.create_atoms(elements=['H', 'H', 'O'],\n", + " positions=[r_H1, r_H2, r_O],\n", + " cell=cell, pbc=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "6844a0f6-ee48-48cd-afa5-85ed7178d3ea", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/cmmc/u/chandu/programs/pyiron_mpie/pyiron_atomistics/pyiron_atomistics/lammps/base.py:210: UserWarning: WARNING: Non-'metal' units are not fully supported. Your calculation should run OK, but results may not be saved in pyiron units.\n", + " \"WARNING: Non-'metal' units are not fully supported. Your calculation should run OK, but \"\n", + "/cmmc/u/chandu/programs/pyiron_mpie/pyiron_atomistics/pyiron_atomistics/lammps/base.py:262: UserWarning: No potential set via job.potential - use default potential, H2O_tip3p\n", + " warnings.warn(\"No potential set via job.potential - use default potential, \" + lst_of_potentials[0])\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The job test was saved and received the ID: 15453531\n" + ] + } + ], + "source": [ + "job_int = pr.create.job.Lammps(\"test\", delete_existing_job=True)\n", + "\n", + "job_int.structure = water\n", + "job_int.interactive_open()\n", + "job_int.calc_static()\n", + "job_int.run()\n", + "job_int.interactive_close()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "0de0c792-5af1-4494-93a1-070ef178a006", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The job test_ni was saved and received the ID: 15453533\n" + ] + } + ], + "source": [ + "job = pr.create.job.Lammps(\"test_ni\", delete_existing_job=True)\n", + "job.structure = water\n", + "job.calc_static()\n", + "job.run()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "9cb4ef26-282b-4609-ad9f-a57451f8167c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "cells\n", + "energy_pot\n", + "energy_tot\n", + "forces\n", + "indices\n", + "positions\n", + "pressures\n", + "steps\n", + "temperature\n", + "volume\n" + ] + } + ], + "source": [ + "# Assert that the unit converstions work even in the interactive mode\n", + "\n", + "int_nodes = job_int[\"output/generic\"].list_nodes()\n", + "usual_nodes = job[\"output/generic\"].list_nodes()\n", + "for node in int_nodes:\n", + " if node in usual_nodes:\n", + " print(node)\n", + " assert np.allclose(job_int[\"output/generic/\" + node], job[\"output/generic/\" + node])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fb10b1ab-f06d-41fd-a1f7-00c31569b2cf", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 2360c365360b330fefc47c7d753411d1a46a5bda Mon Sep 17 00:00:00 2001 From: sudarsan1989 Date: Thu, 29 Jul 2021 11:01:31 +0200 Subject: [PATCH 08/12] Use the dictionary from the units module --- pyiron_atomistics/lammps/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyiron_atomistics/lammps/base.py b/pyiron_atomistics/lammps/base.py index 09928be02..381ed799e 100644 --- a/pyiron_atomistics/lammps/base.py +++ b/pyiron_atomistics/lammps/base.py @@ -19,7 +19,7 @@ from pyiron_atomistics.lammps.control import LammpsControl from pyiron_atomistics.lammps.potential import LammpsPotential from pyiron_atomistics.lammps.structure import LammpsStructure, UnfoldingPrism -from pyiron_atomistics.lammps.units import UnitConverter +from pyiron_atomistics.lammps.units import UnitConverter, LAMMPS_UNIT_CONVERSIONS __author__ = "Joerg Neugebauer, Sudarsan Surendralal, Jan Janssen" @@ -77,7 +77,7 @@ def units(self): @units.setter def units(self, val): - allowed_types = ['metal', 'real', 'si', 'cgs', 'lj'] + allowed_types = LAMMPS_UNIT_CONVERSIONS.keys() if val in allowed_types: self.input.control["units"] = val else: From cccf39cc86c77d19897e8abe2af6c5bb85e44e83 Mon Sep 17 00:00:00 2001 From: sudarsan1989 Date: Thu, 29 Jul 2021 11:09:33 +0200 Subject: [PATCH 09/12] :white_check_mark: Adding tests --- tests/lammps/test_base.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/lammps/test_base.py b/tests/lammps/test_base.py index 85798a140..f43fa0d6a 100644 --- a/tests/lammps/test_base.py +++ b/tests/lammps/test_base.py @@ -426,6 +426,7 @@ def test_dump_parser_water(self): self.assertTrue(np.allclose(neigh_traj_obj.indices, neigh_traj_obj_loaded.indices)) self.assertTrue(np.allclose(neigh_traj_obj.distances, neigh_traj_obj_loaded.distances)) self.assertTrue(np.allclose(neigh_traj_obj.vecs, neigh_traj_obj_loaded.vecs)) + self.assertTrue(self.job_water_dump.units, "real") def test_dump_parser(self): structure = Atoms( @@ -722,6 +723,15 @@ def test_potential_check(self): potential['Species'][0][0] = 'Al' self.job.potential = potential # shouldn't raise ValueError + def test_units(self): + self.assertTrue(self.job.units, "metal") + self.job.units = "real" + self.assertTrue(self.job.units, "real") + + def setter(x): + self.job.units = x + self.assertRaises(ValueError, setter, "nonsense") + if __name__ == "__main__": unittest.main() From 2fe41b5c79644a69172596654ac53341b7b258d0 Mon Sep 17 00:00:00 2001 From: sudarsan1989 Date: Thu, 29 Jul 2021 11:10:23 +0200 Subject: [PATCH 10/12] Remove decorator definition --- pyiron_atomistics/lammps/units.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/pyiron_atomistics/lammps/units.py b/pyiron_atomistics/lammps/units.py index f80db4bcb..6eb3e1aed 100644 --- a/pyiron_atomistics/lammps/units.py +++ b/pyiron_atomistics/lammps/units.py @@ -214,21 +214,3 @@ def convert_array_to_pyiron_units(self, array, label): warnings.warn(message="Warning: Couldn't determine the LAMMPS to pyiron unit conversion type of quantity " "{}. Returning un-normalized quantity".format(label)) return array - - -class UnitsDecorator: - - def __init__(self): - self._units = None - self._label = None - - def __call__(self, units, label): - self._label = label - return self.__decorate_to_pyiron - - def __decorate_to_pyiron(self, function): - @functools.wraps(function) - def dec(*args, **kwargs): - return UnitConverter(self._units).convert_array_to_pyiron_units(function(*args, **kwargs), - label=self._label) - return dec From 1b1aec9db732cdf6069cad777544ccba2dc2384e Mon Sep 17 00:00:00 2001 From: sudarsan1989 Date: Thu, 29 Jul 2021 17:50:26 +0200 Subject: [PATCH 11/12] Explain the notebook a bit --- notebooks/integration/interactive_units.ipynb | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/notebooks/integration/interactive_units.ipynb b/notebooks/integration/interactive_units.ipynb index bdf811460..e964f9d66 100644 --- a/notebooks/integration/interactive_units.ipynb +++ b/notebooks/integration/interactive_units.ipynb @@ -1,9 +1,15 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebooks compares the outputs from two static LAMMPS jobs one of which is an interactive job. This is to ensure that the outputs from interactive and non-interactive jobs are consistent." + ] + }, { "cell_type": "code", "execution_count": 1, - "id": "adb8a78b-112c-4d6f-8a50-aba827f48a45", "metadata": {}, "outputs": [], "source": [ @@ -14,7 +20,6 @@ { "cell_type": "code", "execution_count": 2, - "id": "63521ebb-b6fd-4ff0-ba5e-67d1bff34222", "metadata": {}, "outputs": [], "source": [ @@ -25,7 +30,6 @@ { "cell_type": "code", "execution_count": 3, - "id": "47c6a732-f0b9-4b92-b409-fb824d8fe2f5", "metadata": {}, "outputs": [], "source": [ @@ -42,7 +46,6 @@ { "cell_type": "code", "execution_count": 4, - "id": "6844a0f6-ee48-48cd-afa5-85ed7178d3ea", "metadata": {}, "outputs": [ { @@ -59,13 +62,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "The job test was saved and received the ID: 15453531\n" + "The job test was saved and received the ID: 15458456\n" ] } ], "source": [ + "# Interactive job\n", "job_int = pr.create.job.Lammps(\"test\", delete_existing_job=True)\n", - "\n", "job_int.structure = water\n", "job_int.interactive_open()\n", "job_int.calc_static()\n", @@ -76,18 +79,18 @@ { "cell_type": "code", "execution_count": 5, - "id": "0de0c792-5af1-4494-93a1-070ef178a006", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "The job test_ni was saved and received the ID: 15453533\n" + "The job test_ni was saved and received the ID: 15458457\n" ] } ], "source": [ + "# Non-interactive job\n", "job = pr.create.job.Lammps(\"test_ni\", delete_existing_job=True)\n", "job.structure = water\n", "job.calc_static()\n", @@ -96,8 +99,7 @@ }, { "cell_type": "code", - "execution_count": 8, - "id": "9cb4ef26-282b-4609-ad9f-a57451f8167c", + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -131,7 +133,6 @@ { "cell_type": "code", "execution_count": null, - "id": "fb10b1ab-f06d-41fd-a1f7-00c31569b2cf", "metadata": {}, "outputs": [], "source": [] From 7751f519a80363f9d72e1a388a269221986ed812 Mon Sep 17 00:00:00 2001 From: sudarsan1989 Date: Thu, 29 Jul 2021 18:28:14 +0200 Subject: [PATCH 12/12] pep8 fix --- pyiron_atomistics/lammps/units.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyiron_atomistics/lammps/units.py b/pyiron_atomistics/lammps/units.py index 6eb3e1aed..1fd7f7d02 100644 --- a/pyiron_atomistics/lammps/units.py +++ b/pyiron_atomistics/lammps/units.py @@ -5,7 +5,6 @@ import numpy as np import scipy.constants as spc import warnings -import functools __author__ = "Joerg Neugebauer, Sudarsan Surendralal, Jan Janssen" __copyright__ = (