From a6e268ec198c7b3d253e59065a20f17a2db6b87b Mon Sep 17 00:00:00 2001
From: Adam Fekete <adam.fekete@kcl.ac.uk>
Date: Mon, 22 Oct 2018 15:06:53 +0100
Subject: [PATCH] adding helper functions to quip tutorial

---
 quip-gap/scripts/Preprocess.py     | 130 +++++++++++++++++++
 quip-gap/scripts/Reader.py         | 197 +++++++++++++++++++++++++++++
 quip-gap/scripts/Visualise.py      | 154 ++++++++++++++++++++++
 quip-gap/scripts/Visualise_quip.py | 154 ++++++++++++++++++++++
 quip-gap/scripts/__init__.py       |   0
 5 files changed, 635 insertions(+)
 create mode 100644 quip-gap/scripts/Preprocess.py
 create mode 100644 quip-gap/scripts/Reader.py
 create mode 100644 quip-gap/scripts/Visualise.py
 create mode 100644 quip-gap/scripts/Visualise_quip.py
 create mode 100644 quip-gap/scripts/__init__.py

diff --git a/quip-gap/scripts/Preprocess.py b/quip-gap/scripts/Preprocess.py
new file mode 100644
index 0000000..6b29c47
--- /dev/null
+++ b/quip-gap/scripts/Preprocess.py
@@ -0,0 +1,130 @@
+from pathlib import Path
+from pprint import pprint
+import json
+from ase.io import read, write
+from ase.geometry import crystal_structure_from_cell
+import numpy as np
+# import numpy.linalg as la
+
+import matplotlib.pyplot as plt
+from scipy.interpolate import interp1d
+
+
+class Calculation(object):
+    def __init__(self, *args, **kwargs):
+        self.filepath = kwargs.pop('filepath', None)
+        self.parameters = kwargs
+
+    def get_data(self, index=-1):
+        return read(str(self.filepath))
+
+    @classmethod
+    def from_path(cls, path: Path):
+        with (path / 'gb.json').open() as data_file:
+            gb_data = json.load(data_file)
+
+        with (path / 'subgb.json').open() as data_file:
+            subgb_data = json.load(data_file)
+
+        # print(gb_data['angle'])
+
+        filename = subgb_data['name'] + "_traj.xyz"
+        filepath = (path / filename).resolve()
+        # configuration = read(str((path / filename).resolve()), index=index)
+        # # gb = read(str((path / filename).resolve()), index=-1)
+
+
+        # print('{:=^60}'.format(' '+str(path)+' '))
+        #
+        # print('{:-^40}'.format(' gb.json '))
+        # pprint(gb_data)
+        #
+        # print('{:-^40}'.format(' subgb.json '))
+        # pprint(subgb_data)
+
+        # print('{:-^40}'.format(' initial '))
+        #
+        # force_norm = np.linalg.norm(gb_initial.arrays['force'], axis=1)
+        # force_mean = np.mean(force_norm)
+        # force_std = np.std(force_norm)
+        #
+        # print('Force mean: {:f}, std: {:f}'.format(force_mean, force_std))
+        # pprint(gb_initial.calc.results)
+        #
+        # print('{:-^40}'.format(' final '))
+        #
+        # force_norm = np.linalg.norm(gb_final.arrays['force'], axis=1)
+        # force_mean = np.mean(force_norm)
+        # force_std = np.std(force_norm)
+        #
+        # print('Force mean: {:f}, std: {:f}'.format(force_mean, force_std))
+        # pprint(gb_final.calc.results)
+
+
+        return cls(**{**gb_data, **subgb_data}, filepath=filepath)
+
+
+if __name__ == '__main__':
+
+    # Read grain boundary database
+    dirpath = Path('../GB_alphaFe_001')
+
+    calculations = {
+        'tilt': [Calculation.from_path(calc_dir) for calc_dir in (dirpath / 'tilt').iterdir() if calc_dir.is_dir()],
+        'twist': [Calculation.from_path(calc_dir) for calc_dir in (dirpath / 'twist').iterdir() if calc_dir.is_dir()]
+    }
+
+    # potential energy of the perfect crystal according to a specific potential
+    potential_energy_per_atom = -4.01298214176      # alpha-Fe PotBH
+    eV = 1.6021766208e-19
+    Angstrom = 1.e-10
+
+    angles, energies = [], []
+    for calc in sorted(calculations['tilt'], key=lambda item: item.parameters['angle']):
+
+        # E_gb = calc.parameters.get('E_gb', None)
+        #
+        # if E_gb is None:
+        #     print(calc.filepath)
+        #     print(calc.parameters['converged'])
+        # else:
+
+        # energy = 16.02 / (2 * calc.parameters['A'] ) * \
+        #     (E_gb - potential_energy_per_atom * calc.parameters['n_at'])
+
+        if calc.parameters.get('converged', None):
+            # energy = 16.02 / (2 * calc.parameters['A'] ) * \
+            #     (calc.parameters.get('E_gb') - potential_energy_per_atom * calc.parameters['n_at'])
+            #
+            atoms = calc.get_data()
+            cell = atoms.get_cell()
+            A = cell[0, 0] * cell[1, 1]
+
+            energy = (
+                eV / Angstrom**2 /
+                (2 * A) *
+                (atoms.get_potential_energy() - potential_energy_per_atom * len(atoms))
+            )
+
+            write(calc.filepath.name, atoms)
+
+
+            # print(energy)
+            # print(calc.parameters['converged'])
+            # print(data.get_potential_energy())  # data.get_total_energy() == data.get_potential_energy()
+            # energies.append(calc.parameters['E_gb'] - data.get_total_energy())
+            energies.append(energy)
+            angles.append(calc.parameters['angle'] * 180.0 / np.pi)
+        else:
+            print("not converged: ", calc.filepath)
+
+
+    plt.bar(angles, energies)
+
+    # x_smooth = np.linspace(min(angles), max(angles), 1000, endpoint=True)
+    # f = interp1d(angles, energies, kind='cubic')
+    # plt.plot(x_smooth, f(x_smooth), '-')
+
+    plt.show()
+
+
diff --git a/quip-gap/scripts/Reader.py b/quip-gap/scripts/Reader.py
new file mode 100644
index 0000000..cf9a1ab
--- /dev/null
+++ b/quip-gap/scripts/Reader.py
@@ -0,0 +1,197 @@
+from pathlib import Path
+from pprint import pprint
+import json
+from ase.io import read, write
+from ase.geometry import crystal_structure_from_cell
+import numpy as np
+# import numpy.linalg as la
+
+import matplotlib.pyplot as plt
+from scipy.interpolate import interp1d
+
+
+class Calculation(object):
+    def __init__(self, *args, **kwargs):
+        self.filepath = kwargs.pop('filepath', None)
+        self.parameters = kwargs
+
+    def get_data(self, index=-1):
+        return read(str(self.filepath), index=index)
+
+    @classmethod
+    def from_path(cls, path: Path, index=-1):
+        with (path / 'gb.json').open() as data_file:
+            gb_data = json.load(data_file)
+
+        with (path / 'subgb.json').open() as data_file:
+            subgb_data = json.load(data_file)
+
+        # print(gb_data['angle'])
+
+        filename = subgb_data['name'] + "_traj.xyz"
+        filepath = (path / filename).resolve()
+        # configuration = read(str((path / filename).resolve()), index=index)
+        # # gb = read(str((path / filename).resolve()), index=-1)
+
+
+        # print('{:=^60}'.format(' '+str(path)+' '))
+        #
+        # print('{:-^40}'.format(' gb.json '))
+        # pprint(gb_data)
+        #
+        # print('{:-^40}'.format(' subgb.json '))
+        # pprint(subgb_data)
+
+        # print('{:-^40}'.format(' initial '))
+        #
+        # force_norm = np.linalg.norm(gb_initial.arrays['force'], axis=1)
+        # force_mean = np.mean(force_norm)
+        # force_std = np.std(force_norm)
+        #
+        # print('Force mean: {:f}, std: {:f}'.format(force_mean, force_std))
+        # pprint(gb_initial.calc.results)
+        #
+        # print('{:-^40}'.format(' final '))
+        #
+        # force_norm = np.linalg.norm(gb_final.arrays['force'], axis=1)
+        # force_mean = np.mean(force_norm)
+        # force_std = np.std(force_norm)
+        #
+        # print('Force mean: {:f}, std: {:f}'.format(force_mean, force_std))
+        # pprint(gb_final.calc.results)
+
+
+        return cls(**{**gb_data, **subgb_data}, filepath=filepath)
+
+
+if __name__ == '__main__':
+
+    # Read grain boundary database
+    dirpath = Path('../GB_alphaFe_001')
+
+    calculations = {
+        'tilt': [Calculation.from_path(calc_dir) for calc_dir in (dirpath / 'tilt').iterdir() if calc_dir.is_dir()],
+        'twist': [Calculation.from_path(calc_dir) for calc_dir in (dirpath / 'twist').iterdir() if calc_dir.is_dir()]
+    }
+
+    # potential energy of the perfect crystal according to a specific potential
+    potential_energy_per_atom = -4.01298214176      # alpha-Fe PotBH
+    eV = 1.6021766208e-19
+    Angstrom = 1.e-10
+
+    angles, energies = [], []
+    for calc in sorted(calculations['tilt'], key=lambda item: item.parameters['angle']):
+        angles.append(calc.parameters['angle'] * 180.0 / np.pi)
+
+
+        energy = 16.02 / (2 * calc.parameters['A'] ) * \
+            (calc.parameters['E_gb'] - potential_energy_per_atom * calc.parameters['n_at'])
+
+        atoms = calc.get_data()
+        cell = atoms.get_cell()
+        A = cell[0, 0] * cell[1, 1]
+
+        energy = (
+            eV / Angstrom**2 /
+            (2 * A) *
+            (atoms.get_total_energy() - potential_energy_per_atom * len(atoms))
+        )
+
+        print(energy)
+        # print(data.get_potential_energy())  # data.get_total_energy() == data.get_potential_energy()
+        # energies.append(calc.parameters['E_gb'] - data.get_total_energy())
+        energies.append(energy)
+
+
+    plt.bar(angles, energies)
+
+    # x_smooth = np.linspace(min(angles), max(angles), 1000, endpoint=True)
+    # f = interp1d(angles, energies, kind='cubic')
+    # plt.plot(x_smooth, f(x_smooth), '-')
+
+    plt.show()
+
+
+# 00171501160
+# {
+#   "A": 128.39243418897394,
+#   "gbid": "00171501160",
+#   "sigma_csl": 0,
+#   "H": 181.47714791126677,
+#   "n_at": 514,
+#   "orientation_axis": [
+#     0,
+#     0,
+#     1
+#   ],
+#   "boundary_plane": [
+#     1.0,
+#     16.0,
+#     0.0
+#   ],
+#   "coincident_sites": 2,
+#   "angle": 0.12479104151759456,
+#   "type": "symmetric tilt boundary",
+#   "zplanes": [
+#     45.2801,
+#     136.0193
+#   ]
+# }
+# {
+#   "A": 1540.7092103178,
+#   "E_gb": -98740.56229920377,
+#   "gbid": "00171501160_v6bxv2_tv0.4bxv0.1_d1.4z",
+#   "name": "00171501160_v6bxv2_tv0.4bxv0.1_d1.4z",
+#   "area": 1540.7092103178,
+#   "H": 181.47714791,
+#   "n_at": 24636,
+#   "rbt": [
+#     0.4,
+#     0.1
+#   ],
+#   "rcut": 1.4,
+#   "converged": true,
+#   "param_file": "PotBH.xml",
+#   "E_gb_init": -97920.0696568818
+# }
+
+# PotBH
+#
+# Min. Energy: 0.6408 J/m^{2}
+# Min. Energy Structure
+# Microscopic Degrees of Freedom (x,y, Cutoff Criterion)
+# (0.4, 0.1, 1.4)
+# (0.4, 0.1, 1.5)
+# Max. Energy:1.5965 J/m^{2}
+# Max. Energy Structure
+# Microscopic Degrees of Freedom (x,y, Cutoff Criterion)
+# (0.0, 0.1, 2.1)
+
+#
+#
+# models.py
+# gb_ener = 16.02*((sub_dict['E_gb']-(-4.2731*sub_dict['n_at']))/(2*sub_dict['A']))
+#
+# gb_ener = 16.02*((gb_dict['E_gb']-(ener_per_atom[param_file]*float(gb_dict['n_at'])))/(2*gb_dict['A']))
+#
+# calc_gap.py
+# subgbs = [(16.02*(subgb['E_gb']-float(subgb['n_at']*ener_per_atom['PotBH.xml']))/(2.0*subgb['area']), subgb) for subgb in subgbs]
+#
+# calc_energy.py
+# def calc_e_gb(at, E_bulk):
+#   cell = at.get_cell()
+#   A    = cell[0,0]*cell[1,1]
+#   E_gb = (at.get_potential_energy()-(at.n*(E_bulk)))/(2.*A)
+#   print A
+#   print at.get_potential_energy()
+#   print E_gb, 'eV/A^2'
+#   E_gb = 16.02*(at.get_potential_energy()-(at.n*(E_bulk)))/(2.*A)
+    #   print E_gb, 'J/m^2'
+#   return E_gb
+#
+# relax.py
+# E_gb_init   = grain.get_potential_energy()
+# E_gb    = grain.get_potential_energy()
+#
+#
+# ener_per_atom = -4.01298214176
\ No newline at end of file
diff --git a/quip-gap/scripts/Visualise.py b/quip-gap/scripts/Visualise.py
new file mode 100644
index 0000000..1ed65c2
--- /dev/null
+++ b/quip-gap/scripts/Visualise.py
@@ -0,0 +1,154 @@
+import io
+import uuid
+
+from nglview import register_backend, Structure
+from ipywidgets import Dropdown, FloatSlider, IntSlider, HBox, VBox, Output
+
+import matplotlib.pyplot as plt
+import numpy as np
+
+    
+
+@register_backend('ase')
+class MyASEStructure(Structure):
+    def __init__(self, atoms, bfactor=[], occupancy=[]):
+        # super(MyASEStructure, self).__init__()
+        self.ext = 'pdb'
+        self.params = {}
+        self._atoms = atoms
+        self.bfactor = bfactor  # [min, max]
+        self.occupancy = occupancy  # [0, 1]
+        self.id = str(uuid.uuid4())
+
+    def get_structure_string(self):
+        """ PDB file format:
+        CRYST1   16.980   62.517  124.864  90.00  90.00  90.00 P 1
+        MODEL     1
+        ATOM      0   Fe MOL     1      15.431  60.277   6.801  1.00  0.00          FE
+        ATOM      1   Fe MOL     1       1.273   3.392  93.940  1.00  0.00          FE
+        """
+        # with io.StringIO() as stream:
+        #     self.structure.write(stream, format='proteindatabank')
+        #     data = stream.getvalue()
+
+        data = ""
+
+        if self._atoms.get_pbc().any():
+            cellpar = self._atoms.get_cell_lengths_and_angles()
+
+            str_format = 'CRYST1' + '{:9.3f}' * 3 + '{:7.2f}' * 3 + ' P 1\n'
+            data += str_format.format(*cellpar.tolist())
+
+        data += 'MODEL     1\n'
+
+        str_format = 'ATOM  {:5d} {:>4s} MOL     1    {:8.3f}{:8.3f}{:8.3f}{:6.2f}{:6.2f}          {:2s}\n'
+        for index, atom in enumerate(self._atoms):
+            data += str_format.format(
+                index,
+                atom.symbol,
+                atom.position[0].tolist(),
+                atom.position[1].tolist(),
+                atom.position[2].tolist(),
+                self.occupancy[index] if index <= len(self.occupancy) - 1 else 1.0,
+                self.bfactor[index] if index <= len(self.bfactor) - 1 else 1.0,
+                atom.symbol.upper()
+            )
+
+        data += 'ENDMDL\n'
+
+        return data
+
+
+def ViewStructure(atoms):
+    import nglview
+
+    view = nglview.NGLWidget()
+    
+    structure = MyASEStructure(atoms)
+    view.add_structure(structure)
+    
+    return view
+
+
+
+class AtomViewer(object):
+    def __init__(self, atoms, data=[], xsize=1000, ysize=500):
+        self.view = self._init_nglview(atoms, data, xsize, ysize)
+
+        self.widgets = {
+            'radius': FloatSlider(
+                value=0.8, min=0.0, max=1.5, step=0.01,
+                description='Ball size'
+            ),
+            'color_scheme': Dropdown(description='Solor scheme:'),
+            'colorbar': Output()
+        }
+        self.show_colorbar(data)
+
+        self.widgets['radius'].observe(self._update_repr)
+
+        self.gui = VBox([
+            self.view,
+            self.widgets['colorbar'],
+            self.widgets['radius']])
+
+    def _update_repr(self, chg=None):
+        self.view.update_spacefill(
+            radiusType='radius',
+            radius=self.widgets['radius'].value
+        )
+
+    def show_colorbar(self, data):
+        with self.widgets['colorbar']:
+            # Have colormaps separated into categories:
+            # http://matplotlib.org/examples/color/colormaps_reference.html
+            cmap = 'rainbow'
+
+            fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(16, 2))
+            img = ax1.imshow([[min(data), max(data)]], aspect='auto', cmap=plt.get_cmap(cmap))
+            ax1.remove()
+            cbar = fig.colorbar(img, cax=ax2, orientation='horizontal')
+
+            plt.show()
+
+    @staticmethod
+    def _init_nglview(atoms, data, xsize, ysize):
+        import nglview
+
+        view = nglview.NGLWidget(gui=False)
+        view._remote_call(
+            'setSize',
+            target='Widget',
+            args=[
+                '{:d}px'.format(xsize),
+                '{:d}px'.format(ysize)
+            ]
+        )
+
+        data = np.max(data)-data
+
+        structure = MyASEStructure(atoms, bfactor=data)
+        view.add_structure(structure)
+
+        view.clear_representations()
+        view.add_unitcell()
+
+        view.add_spacefill(
+            # radiusType='radius',
+            # radius=1.0,
+            color_scheme='bfactor',
+            color_scale='rainbow'
+        )
+        view.update_spacefill(
+            radiusType='radius',
+            radius=1.0
+        )
+
+
+        # update camera type
+        view.control.spin([1, 0, 0], np.pi / 2)
+        view.control.spin([0, 0, 1], np.pi / 2)
+        view.camera = 'orthographic'
+        view.center()
+
+        return view
diff --git a/quip-gap/scripts/Visualise_quip.py b/quip-gap/scripts/Visualise_quip.py
new file mode 100644
index 0000000..1ed65c2
--- /dev/null
+++ b/quip-gap/scripts/Visualise_quip.py
@@ -0,0 +1,154 @@
+import io
+import uuid
+
+from nglview import register_backend, Structure
+from ipywidgets import Dropdown, FloatSlider, IntSlider, HBox, VBox, Output
+
+import matplotlib.pyplot as plt
+import numpy as np
+
+    
+
+@register_backend('ase')
+class MyASEStructure(Structure):
+    def __init__(self, atoms, bfactor=[], occupancy=[]):
+        # super(MyASEStructure, self).__init__()
+        self.ext = 'pdb'
+        self.params = {}
+        self._atoms = atoms
+        self.bfactor = bfactor  # [min, max]
+        self.occupancy = occupancy  # [0, 1]
+        self.id = str(uuid.uuid4())
+
+    def get_structure_string(self):
+        """ PDB file format:
+        CRYST1   16.980   62.517  124.864  90.00  90.00  90.00 P 1
+        MODEL     1
+        ATOM      0   Fe MOL     1      15.431  60.277   6.801  1.00  0.00          FE
+        ATOM      1   Fe MOL     1       1.273   3.392  93.940  1.00  0.00          FE
+        """
+        # with io.StringIO() as stream:
+        #     self.structure.write(stream, format='proteindatabank')
+        #     data = stream.getvalue()
+
+        data = ""
+
+        if self._atoms.get_pbc().any():
+            cellpar = self._atoms.get_cell_lengths_and_angles()
+
+            str_format = 'CRYST1' + '{:9.3f}' * 3 + '{:7.2f}' * 3 + ' P 1\n'
+            data += str_format.format(*cellpar.tolist())
+
+        data += 'MODEL     1\n'
+
+        str_format = 'ATOM  {:5d} {:>4s} MOL     1    {:8.3f}{:8.3f}{:8.3f}{:6.2f}{:6.2f}          {:2s}\n'
+        for index, atom in enumerate(self._atoms):
+            data += str_format.format(
+                index,
+                atom.symbol,
+                atom.position[0].tolist(),
+                atom.position[1].tolist(),
+                atom.position[2].tolist(),
+                self.occupancy[index] if index <= len(self.occupancy) - 1 else 1.0,
+                self.bfactor[index] if index <= len(self.bfactor) - 1 else 1.0,
+                atom.symbol.upper()
+            )
+
+        data += 'ENDMDL\n'
+
+        return data
+
+
+def ViewStructure(atoms):
+    import nglview
+
+    view = nglview.NGLWidget()
+    
+    structure = MyASEStructure(atoms)
+    view.add_structure(structure)
+    
+    return view
+
+
+
+class AtomViewer(object):
+    def __init__(self, atoms, data=[], xsize=1000, ysize=500):
+        self.view = self._init_nglview(atoms, data, xsize, ysize)
+
+        self.widgets = {
+            'radius': FloatSlider(
+                value=0.8, min=0.0, max=1.5, step=0.01,
+                description='Ball size'
+            ),
+            'color_scheme': Dropdown(description='Solor scheme:'),
+            'colorbar': Output()
+        }
+        self.show_colorbar(data)
+
+        self.widgets['radius'].observe(self._update_repr)
+
+        self.gui = VBox([
+            self.view,
+            self.widgets['colorbar'],
+            self.widgets['radius']])
+
+    def _update_repr(self, chg=None):
+        self.view.update_spacefill(
+            radiusType='radius',
+            radius=self.widgets['radius'].value
+        )
+
+    def show_colorbar(self, data):
+        with self.widgets['colorbar']:
+            # Have colormaps separated into categories:
+            # http://matplotlib.org/examples/color/colormaps_reference.html
+            cmap = 'rainbow'
+
+            fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(16, 2))
+            img = ax1.imshow([[min(data), max(data)]], aspect='auto', cmap=plt.get_cmap(cmap))
+            ax1.remove()
+            cbar = fig.colorbar(img, cax=ax2, orientation='horizontal')
+
+            plt.show()
+
+    @staticmethod
+    def _init_nglview(atoms, data, xsize, ysize):
+        import nglview
+
+        view = nglview.NGLWidget(gui=False)
+        view._remote_call(
+            'setSize',
+            target='Widget',
+            args=[
+                '{:d}px'.format(xsize),
+                '{:d}px'.format(ysize)
+            ]
+        )
+
+        data = np.max(data)-data
+
+        structure = MyASEStructure(atoms, bfactor=data)
+        view.add_structure(structure)
+
+        view.clear_representations()
+        view.add_unitcell()
+
+        view.add_spacefill(
+            # radiusType='radius',
+            # radius=1.0,
+            color_scheme='bfactor',
+            color_scale='rainbow'
+        )
+        view.update_spacefill(
+            radiusType='radius',
+            radius=1.0
+        )
+
+
+        # update camera type
+        view.control.spin([1, 0, 0], np.pi / 2)
+        view.control.spin([0, 0, 1], np.pi / 2)
+        view.camera = 'orthographic'
+        view.center()
+
+        return view
diff --git a/quip-gap/scripts/__init__.py b/quip-gap/scripts/__init__.py
new file mode 100644
index 0000000..e69de29
-- 
GitLab