Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Interactive units #295

Merged
merged 12 commits into from
Aug 6, 2021
161 changes: 161 additions & 0 deletions notebooks/integration/interactive_units.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
{
sudarsan-surendralal marked this conversation as resolved.
Show resolved Hide resolved
"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
}
28 changes: 25 additions & 3 deletions pyiron_atomistics/lammps/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']
sudarsan-surendralal marked this conversation as resolved.
Show resolved Hide resolved
if val in allowed_types:
self.input.control["units"] = val
else:
raise ValueError("'{}' is not a valid LAMMPS unit")

@property
def bond_dict(self):
"""
Expand Down Expand Up @@ -449,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.")
Expand Down Expand Up @@ -537,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):
Expand Down Expand Up @@ -905,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 = {}
Expand Down
29 changes: 21 additions & 8 deletions pyiron_atomistics/lammps/interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from pylammpsmpi import LammpsLibrary
except ImportError:
pass
from pyiron_atomistics.lammps.units import UnitConverter

__author__ = "Osamu Waseda, Jan Janssen"
__copyright__ = (
Expand Down Expand Up @@ -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):
Expand All @@ -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],
Expand All @@ -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)
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand All @@ -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):
"""
Expand All @@ -595,6 +607,7 @@ def interactive_stress_getter(self):
return ss

def interactive_pressures_getter(self):
uc = UnitConverter(units=self.units)
pp = np.array(
[
[
Expand All @@ -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():
Expand Down
21 changes: 20 additions & 1 deletion pyiron_atomistics/lammps/units.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__ = (
Expand Down Expand Up @@ -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():
Expand All @@ -213,3 +214,21 @@ 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
2 changes: 1 addition & 1 deletion tests/lammps/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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()