Skip to content

Commit

Permalink
Add pages to show multiple applications (results and details) (#29)
Browse files Browse the repository at this point in the history
* update readme

* add new button

* add stand-alone results page

* add stand-alone details page

* update export scripts

* update aiida-core version

Co-authored-by: Leopold Talirz <[email protected]>
  • Loading branch information
danieleongari and ltalirz authored Sep 7, 2020
1 parent 9bf7a34 commit e284170
Show file tree
Hide file tree
Showing 23 changed files with 1,754 additions and 64 deletions.
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ WORKDIR /app/
COPY figure ./figure
COPY detail ./detail
COPY select-figure ./select-figure
COPY pipeline_config ./pipeline_config
COPY details ./details
COPY results ./results
RUN ln -s /app/jmol-14.29.22/jsmol ./detail/static/jsmol
COPY setup.py ./
RUN pip install -e .
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
Use this app to generate interactive visualizations like [these](https://www.materialscloud.org/discover/cofs#mcloudHeader)
for atomic structures and their properties.

### UPDATE (September 2020)
Now this package contains the `results` and `details` pages copied from `nanoporous_screening` to display different
applications for COFs. This will be used to produce a permanent reference for the ACS Central Science's Outlook.

Not to have a dependency to the `nanoporous_screening/pipeline` package, the needed functions are copied in the
`pipeline_emul.py` files in the `results` and `details` directories (two identical files).

## Inner working

For each COF we create a group, e.g., `discover_curated_cofs/05001N2` that contains all the nodes that are relevant for that structure.
Expand Down
4 changes: 2 additions & 2 deletions detail/structure.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# pylint: disable=unsubscriptable-object, too-many-locals


def structure_jsmol(cif_str):
from jsmol_bokeh_extension import JSMol
import bokeh.models as bmd
Expand All @@ -24,8 +25,7 @@ def structure_jsmol(cif_str):
{}
end "cifstring"
""".format(cif_str)
)
""".format(cif_str))

applet = JSMol(
width=600,
Expand Down
Empty file added details/__init__.py
Empty file.
67 changes: 67 additions & 0 deletions details/dft_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from bokeh.plotting import figure
import bokeh.models as bmd


def get_startindex(steps):
'''Take a list of steps and decide starting indices (and final number of steps).'''
start_indices = []
for i, step in enumerate(steps):
if i == 0:
start_indices.append(0)
else:
if step <= steps[i - 1]:
start_indices.append(i)
start_indices.append(len(steps))
return start_indices


def plot_energy_steps(dftopt_out): #pylint: disable=too-many-locals
"""Plot the total energy graph."""

units = 'eV'
ha2u = {'eV': 27.211399}

out_dict = dftopt_out.get_dict()

tooltips = [("Step (total)", "@index"), ("Step (stage)", "@step"), ("Energy", "@energy eV/atom"),
("Energy (dispersion)", "@dispersion_energy_au Ha"), ("SCF converged", "@scf_converged"),
("Cell A", "@cell_a_angs Angs"), ("Cell Vol", "@cell_vol_angs3 Angs^3"),
("MAX Step", "@max_step_au Bohr"), ("Pressure", "@pressure_bar bar")]
hover = bmd.HoverTool(tooltips=tooltips)
TOOLS = ["pan", "wheel_zoom", "box_zoom", "reset", "save", hover]

natoms = out_dict['natoms']
values = [x / natoms * ha2u[units] for x in out_dict['step_info']['energy_au']]
values = [x - min(values) for x in values]

data = bmd.ColumnDataSource(data=dict(
index=range(len(values)),
step=out_dict['step_info']['step'],
energy=values,
dispersion_energy_au=out_dict['step_info']['dispersion_energy_au'],
scf_converged=out_dict['step_info']['scf_converged'],
cell_a_angs=out_dict['step_info']['cell_a_angs'],
cell_vol_angs3=out_dict['step_info']['cell_vol_angs3'],
max_step_au=out_dict['step_info']['max_step_au'],
pressure_bar=out_dict['step_info']['pressure_bar'],
))

p = figure(tools=TOOLS, title='Energy profile of the DFT minimization', height=350, width=550)

p.xgrid.grid_line_color = None
p.xaxis.axis_label = 'Steps'
p.yaxis.axis_label = 'Energy ({}/atom)'.format(units)

# Colored background
colors = ['red', 'orange', 'green', 'yellow', 'cyan', 'pink', 'palegreen']
start = 0
for i, steps in enumerate(out_dict['stage_info']['nsteps']):
end = start + steps
p.add_layout(bmd.BoxAnnotation(left=start, right=end, fill_alpha=0.2, fill_color=colors[i]))
start = end

# Trace line and markers
p.line('index', 'energy', source=data, line_color='blue')
p.circle('index', 'energy', source=data, line_color='blue', size=3)

return p
135 changes: 135 additions & 0 deletions details/isotherms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import numpy as np
import pandas as pd
from bokeh.plotting import figure
import bokeh.models as bmd
from pipeline_config import gasses, get_isotherm_nodes
from details.utils import get_provenance_url, get_provenance_link


def plot_isotherms(mat_id): #pylint: disable=too-many-locals
"""Plot figure with all isotherms."""

nodes_dict = get_isotherm_nodes(mat_id)

tooltips = [
("Molecule", "@legend_label"),
("Uptake (mol/kg)", "@q_avg"),
]
hover = bmd.HoverTool(tooltips=tooltips)
tap = bmd.TapTool()
tap.callback = bmd.OpenURL(url=get_provenance_url(uuid="@uuid"))
TOOLS = ["pan", "wheel_zoom", "box_zoom", "reset", "save", hover, tap]

p1 = figure(tools=TOOLS, height=350, width=450)
p1.xaxis.axis_label = 'Pressure (bar)'
p1.yaxis.axis_label = 'Uptake (mol/kg)'

for gas, gas_dict in gasses.items():
if gas not in nodes_dict:
continue

for node in nodes_dict[gas]:
try: # avoid to fail if there are problems with one dict
isot_out = node.get_dict()

if isot_out['is_porous']:
p = isot_out['isotherm']["pressure"] #(bar)
q_avg = isot_out['isotherm']["loading_absolute_average"] #(mol/kg)
q_dev = isot_out['isotherm']["loading_absolute_dev"] #(mol/kg)
q_upper = np.array(q_avg) + np.array(q_dev)
q_lower = np.array(q_avg) - np.array(q_dev)
h_avg = isot_out['isotherm']["enthalpy_of_adsorption_average"] #(kJ/mol)
h_dev = isot_out['isotherm']["enthalpy_of_adsorption_dev"] #(kJ/mol)
# TRICK: use the enthalpy from widom (energy-RT) which is more accurate that the one at 0.001 bar
# (and which also is NaN for weakly interacting systems)
h_avg[0] = isot_out['adsorption_energy_widom_average'] - isot_out['temperature'] / 120.027
h_dev[0] = isot_out['adsorption_energy_widom_dev']
h_upper = np.array(h_avg) + np.array(h_dev)
h_lower = np.array(h_avg) - np.array(h_dev)
else:
p = [0, 100]
q_avg = q_upper = q_lower = h_avg = h_upper = h_lower = [0, 0]

legend_label = "{} ({}K)".format(gas_dict['legend'], int(isot_out['temperature']))

data = dict(p=p,
q_avg=q_avg,
q_upper=q_upper,
q_lower=q_lower,
h_avg=h_avg,
h_upper=h_upper,
h_lower=h_lower,
uuid=[str(node.uuid) for _ in q_avg],
legend_label=[legend_label] * len(p))
source = bmd.ColumnDataSource(data=data)

p1.line(x='p',
y='q_avg',
source=source,
line_color=gas_dict['color'],
line_width=2,
legend_label=legend_label)
p1.circle(x='p', y='q_avg', source=source, color=gas_dict['color'], size=5, legend_label=legend_label)
p1.add_layout(bmd.Whisker(source=source, base="p", upper="q_upper", lower="q_lower"))
except (KeyError, TypeError):
continue

p1.legend.location = "bottom_right"

fig = p1

return fig


def get_widom_df(mat_nodes_dict, select):
"""Geting a df table for all the available Henry coefficients or heat of adsorption at zero pressure.
Options: select = "kh" or "hoa"
"""

property_dict = {"kh": 'henry_coefficient_average', "hoa": 'adsorption_energy_widom_average'}

temp_list = [
77,
198,
298,
300,
]

pd.set_option('display.max_colwidth', None)
df = pd.DataFrame(index=[x['legend'] for x in gasses.values()], columns=["{}K".format(x) for x in temp_list])
df = df.fillna("-")

for tag, node in mat_nodes_dict.items():
try: # will fail if tag has no "_" separation, like "dftopt"
wc, mol = tag.split("_")
except ValueError:
continue

if mol not in gasses:
continue

legend = gasses[mol]['legend']

# the try...except skips problematic or nonporous results
# NOTE: nonporous results can not be shown because the temperature of the calculation is unknown!

try: # avoid to fail if there are problems with one dict
if wc in ['kh', 'isot']:
if select == "kh":
val = '{:.2e}'.format(node[property_dict[select]])
elif select == "hoa":
val = '{:.2f}'.format(node[property_dict[select]])
temperature = "{}K".format(int(node['temperature']))
df.loc[legend, temperature] = val + get_provenance_link(uuid=node.uuid)
elif wc in ['isotmt']:
for i, temp in enumerate(node['temperature']):
if select == "kh":
val = '{:.2e}'.format(node[property_dict[select]][i])
elif select == "hoa":
val = '{:.2f}'.format(node[property_dict[select]][i])
temperature = "{}K".format(int(temp))
df.loc[legend, temperature] = val + get_provenance_link(uuid=node.uuid)
except (KeyError, TypeError):
continue

return df
71 changes: 71 additions & 0 deletions details/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env python

import panel as pn
import pandas as pd

from aiida import load_profile
from details.isotherms import plot_isotherms, get_widom_df
from details.dft_info import plot_energy_steps
from details.structure import structure_jsmol
from details.utils import get_mat_id, get_details_title, get_geom_table, get_appl_table, get_title
from pipeline_config import get_mat_nodes_dict

load_profile()

pn.extension(css_files=['details/static/style.css'])


class DetailView():

def __init__(self):
self.mat_id = get_mat_id()
self.mat_nodes_dict = get_mat_nodes_dict(self.mat_id)
print(">> Display details of MAT_ID:", self.mat_id, self.mat_nodes_dict['orig_cif'])

def title_col(self):
col = pn.Column(width=700)
col.append(pn.pane.Markdown(get_details_title(self.mat_nodes_dict['orig_cif'])))
return col

def structure_col(self):
nodes = self.mat_nodes_dict
col = pn.Column(sizing_mode='stretch_width')
col.append(get_title('Cell optimized structure', uuid=nodes['opt_cif_ddec'].uuid))
col.append(pn.pane.Bokeh(structure_jsmol(nodes['opt_cif_ddec'])))
col.append(get_title('Energy profile during cell optimization', uuid=nodes['dftopt'].uuid))
col.append(pn.pane.Bokeh(plot_energy_steps(dftopt_out=nodes['dftopt'])))
col.append(get_title('Geometric properties', uuid=nodes["opt_zeopp"].uuid))
col.append(pn.pane.Markdown(get_geom_table(nodes["opt_zeopp"])))
return col

def properties_col(self):
col = pn.Column(sizing_mode='stretch_width')
col.append(pn.pane.Markdown('## All computed isotherms'))
col.append(pn.pane.Bokeh(plot_isotherms(self.mat_id), sizing_mode='stretch_width'))
col.append(pn.pane.Markdown("## All computed Henry's coefficients (mol/kg/Pa)"))
with pd.option_context('display.max_colwidth', None):
col.append(get_widom_df(self.mat_nodes_dict, select="kh").to_html(escape=False))
col.append(pn.pane.Markdown("## All computed Heat of adsorption @ infinite dilution (kJ/mol)"))
with pd.option_context('display.max_colwidth', None):
col.append(get_widom_df(self.mat_nodes_dict, select="hoa").to_html(escape=False))
return col

def applications_col(self):
col = pn.Column(sizing_mode='stretch_width')
col.append(pn.pane.Markdown('## Numerical values for all the applications'))
col.append(pn.pane.HTML(get_appl_table(self.mat_nodes_dict)))
return col


dv = DetailView()

tabs = pn.Tabs(tabs_location='left', sizing_mode='stretch_width')
tabs.extend([
("Applications", dv.applications_col()),
("Properties", dv.properties_col()),
("Structure", dv.structure_col()),
])

page = dv.title_col()
page.append(tabs)
page.servable()
Binary file added details/static/aiida-128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions details/static/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
img.provenance-logo {
width: 1em;
vertical-align: middle;
height: auto;
margin-left: 0.3em;
}
37 changes: 37 additions & 0 deletions details/structure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from jsmol_bokeh_extension import JSMol
import bokeh.models as bmd


def structure_jsmol(cif_node):

script_source = bmd.ColumnDataSource()
cif_str = cif_node.get_content()

info = dict(
height="100%",
width="100%",
use="HTML5",
serverURL="https://chemapps.stolaf.edu/jmol/jsmol/php/jsmol.php",
j2sPath="https://chemapps.stolaf.edu/jmol/jsmol/j2s",
#serverURL="https://www.materialscloud.org/discover/scripts/external/jsmol/php/jsmol.php",
#j2sPath="https://www.materialscloud.org/discover/scripts/external/jsmol/j2s",
#serverURL="details/static/jsmol/php/jsmol.php",
#j2sPath="details/static/jsmol/j2s",
script="""
set antialiasDisplay ON;
load data "cifstring"
{}
end "cifstring"
""".format(cif_str))

applet = JSMol(
width=600,
height=600,
script_source=script_source,
info=info,
#js_url="details/static/jsmol/JSmol.min.js",
)

return applet
Loading

0 comments on commit e284170

Please sign in to comment.