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

set xsf as default format for structures visualization #1756

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions aiida/backends/tests/restapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,28 @@ def test_structure_visualization(self):
url,
response, uuid=node_uuid)

def test_xsf_visualization(self):
"""
Get the list of given calculation inputs
"""
from aiida.backends.tests.dataclasses import simplify
node_uuid = self.get_dummy_data()["structuredata"][0]["uuid"]
url = self.get_url_prefix() + '/structures/' + str(
node_uuid) + '/content/visualization?visformat=xsf'
with self.app.test_client() as client:
rv = client.get(url)
response = json.loads(rv.data)
expected_visdata = "CRYSTAL\nPRIMVEC 1\n 2.0000000000 0.0000000000 0.0000000000\n 0.0000000000 2.0000000000 0.0000000000\n 0.0000000000 0.0000000000 2.0000000000\nPRIMCOORD 1\n1 1\n56 0.0000000000 0.0000000000 0.0000000000\n"
self.assertEquals(simplify(response["data"]["visualization"]["str_viz_info"]["data"]),simplify(expected_visdata))
self.assertEquals(response["data"]["visualization"]["str_viz_info"]["format"],"xsf")
self.assertEquals(response["data"]["visualization"]["dimensionality"],
{u'dim': 3, u'value': 8.0, u'label': u'volume'})
self.assertEquals(response["data"]["visualization"]["pbc"], [True,True,True])
self.assertEquals(response["data"]["visualization"]["formula"], "Ba")
RESTApiTestCase.compare_extra_response_data(self, "structures",
url,
response, uuid=node_uuid)

def test_cif(self):
"""
Test download of cif file
Expand Down
114 changes: 114 additions & 0 deletions aiida/orm/data/structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,47 @@ def _get_cif_ase_inline(struct=None, parameters=None):
return {'cif': cif}


def atom_kinds_to_html(atom_kind):
"""

Construct in html format

an alloy with 0.5 Ge, 0.4 Si and 0.1 vacancy is represented as
Ge<sub>0.5</sub> + Si<sub>0.4</sub> + vacancy<sub>0.1</sub>

Args:
atom_kind: a string with the name of the atomic kind, as printed by
kind.get_symbols_string(), e.g. Ba0.80Ca0.10X0.10

Returns:
html code for rendered formula
"""

# Parse the formula (TODO can be made more robust though never fails if
# it takes strings generated with kind.get_symbols_string())
import re
elements = re.findall(r'([A-Z][a-z]*)([0-1][.[0-9]*]?)?', atom_kind)

# Compose the html string
html_formula_pieces = []

for element in elements:

# replace element X by 'vacancy'
species = element[0] if element[0] != 'X' else 'vacancy'
weight = element[1] if element[1] != '' else None

if weight is not None:
html_formula_pieces.append(species + '<sub>' + weight +
'</sub>')
else:
html_formula_pieces.append(species)

html_formula = ' + '.join(html_formula_pieces)

return html_formula


class StructureData(Data):
"""
This class contains the information about a given structure, i.e. a
Expand Down Expand Up @@ -976,6 +1017,79 @@ def _prepare_tcod(self, main_file_name="", **kwargs):
from aiida.tools.dbexporters.tcod import export_cif
return export_cif(self, **kwargs).encode('utf-8'), {}

def _prepare_chemdoodle(self, main_file_name=""):
"""
Write the given structure to a string of format required by ChemDoodle.
"""
import numpy as np
from itertools import product
import json

supercell_factors=[1, 1, 1]

# Get cell vectors and atomic position
lattice_vectors = np.array(self.get_attr('cell'))
base_sites = self.get_attr('sites')

start1 = -int(supercell_factors[0] / 2)
start2 = -int(supercell_factors[1] / 2)
start3 = -int(supercell_factors[2] / 2)

stop1 = start1 + supercell_factors[0]
stop2 = start2 + supercell_factors[1]
stop3 = start3 + supercell_factors[2]

grid1 = range(start1, stop1)
grid2 = range(start2, stop2)
grid3 = range(start3, stop3)

atoms_json = []

# Manual recenter of the structure
center = (lattice_vectors[0] + lattice_vectors[1] +
lattice_vectors[2]) / 2.

for ix, iy, iz in product(grid1, grid2, grid3):
for base_site in base_sites:
shift = (ix * lattice_vectors[0] + iy * lattice_vectors[1] + \
iz * lattice_vectors[2] - center).tolist()

kind_name = base_site['kind_name']
kind_string = self.get_kind(kind_name).get_symbols_string()

atoms_json.append(
{'l': kind_string,
'x': base_site['position'][0] + shift[0],
'y': base_site['position'][1] + shift[1],
'z': base_site['position'][2] + shift[2],
# 'atomic_elements_html': kind_string
'atomic_elements_html': atom_kinds_to_html(kind_string)
})

cell_json = {
"t": "UnitCell",
"i": "s0",
"o": (-center).tolist(),
"x": (lattice_vectors[0] - center).tolist(),
"y": (lattice_vectors[1] - center).tolist(),
"z": (lattice_vectors[2] - center).tolist(),
"xy": (lattice_vectors[0] + lattice_vectors[1]
- center).tolist(),
"xz": (lattice_vectors[0] + lattice_vectors[2]
- center).tolist(),
"yz": (lattice_vectors[1] + lattice_vectors[2]
- center).tolist(),
"xyz": (lattice_vectors[0] + lattice_vectors[1]
+ lattice_vectors[2] - center).tolist(),
}

return_dict = {"s": [cell_json],
"m": [{"a": atoms_json}],
"units": '&Aring;'
}

return json.dumps(return_dict), {}

def _prepare_xyz(self, main_file_name=""):
"""
Write the given structure to a string of format XYZ.
Expand Down
128 changes: 7 additions & 121 deletions aiida/restapi/translator/data/structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,51 +8,10 @@
# For further information please visit http://www.aiida.net #
###########################################################################
from aiida.restapi.translator.data import DataTranslator
from aiida.restapi.common.exceptions import RestValidationError
from aiida.restapi.common.exceptions import RestInputValidationError
from aiida.common.exceptions import LicensingException
import numpy as np

def atom_kinds_to_html(atom_kind):
"""

Construct in html format

an alloy with 0.5 Ge, 0.4 Si and 0.1 vacancy is represented as
Ge<sub>0.5</sub> + Si<sub>0.4</sub> + vacancy<sub>0.1</sub>

Args:
atom_kind: a string with the name of the atomic kind, as printed by
kind.get_symbols_string(), e.g. Ba0.80Ca0.10X0.10

Returns:
html code for rendered formula
"""

# Parse the formula (TODO can be made more robust though never fails if
# it takes strings generated with kind.get_symbols_string())
import re
elements = re.findall(r'([A-Z][a-z]*)([0-1][.[0-9]*]?)?', atom_kind)

# Compose the html string
html_formula_pieces = []

for element in elements:

# replace element X by 'vacancy'
species = element[0] if element[0] != 'X' else 'vacancy'
weight = element[1] if element[1] != '' else None

if weight is not None:
html_formula_pieces.append(species + '<sub>' + weight +
'</sub>')
else:
html_formula_pieces.append(species)

html_formula = ' + '.join(html_formula_pieces)

return html_formula


class StructureDataTranslator(DataTranslator):
"""
Translator relative to resource 'structures' and aiida class StructureData
Expand Down Expand Up @@ -80,98 +39,25 @@ def __init__(self, **kwargs):


@staticmethod
def get_visualization_data(node, format=None, supercell_factors=[1, 1, 1]):
def get_visualization_data(node, format=None):
"""
Returns: data in specified format. If format is not specified returns data
in a format required by chemdoodle to visualize a structure.
in xsf format in order to visualize the structure with JSmol.
"""
response = {}
response["str_viz_info"] = {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here below, I would do (if we want to have 'xsf' as default):

if format is None:
    format = 'xsf' 

and then let the logic take care of the default.
Then, in the else, raise an exception (now instead it returns 'xsf' even if the string is unknown).


if format is None:
format = 'xsf'

if format in node.get_export_formats():
try:
response["str_viz_info"]["data"] = node._exportstring(format)[0]
response["str_viz_info"]["format"] = format
except LicensingException as e:
response = e.message

else:
import numpy as np
from itertools import product


# Validate supercell factors
if type(supercell_factors) is not list:
raise RestValidationError('supercell factors have to be a list of three integers')

for fac in supercell_factors:
if type(fac) is not int:
raise RestValidationError('supercell factors have to be '
'integers')

# Get cell vectors and atomic position
lattice_vectors = np.array(node.get_attr('cell'))
base_sites = node.get_attr('sites')

start1 = -int(supercell_factors[0] / 2)
start2 = -int(supercell_factors[1] / 2)
start3 = -int(supercell_factors[2] / 2)

stop1 = start1 + supercell_factors[0]
stop2 = start2 + supercell_factors[1]
stop3 = start3 + supercell_factors[2]

grid1 = range(start1, stop1)
grid2 = range(start2, stop2)
grid3 = range(start3, stop3)

atoms_json = []

# Manual recenter of the structure
center = (lattice_vectors[0] + lattice_vectors[1] +
lattice_vectors[2])/2.

for ix, iy, iz in product(grid1, grid2, grid3):
for base_site in base_sites:

shift = (ix*lattice_vectors[0] + iy*lattice_vectors[1] + \
iz*lattice_vectors[2] - center).tolist()

kind_name = base_site['kind_name']
kind_string = node.get_kind(kind_name).get_symbols_string()

atoms_json.append(
{'l': kind_string,
'x': base_site['position'][0] + shift[0],
'y': base_site['position'][1] + shift[1],
'z': base_site['position'][2] + shift[2],
# 'atomic_elements_html': kind_string
'atomic_elements_html': atom_kinds_to_html(kind_string)
})

cell_json = {
"t": "UnitCell",
"i": "s0",
"o": (-center).tolist(),
"x": (lattice_vectors[0]-center).tolist(),
"y": (lattice_vectors[1]-center).tolist(),
"z": (lattice_vectors[2]-center).tolist(),
"xy": (lattice_vectors[0] + lattice_vectors[1]
- center).tolist(),
"xz": (lattice_vectors[0] + lattice_vectors[2]
- center).tolist(),
"yz": (lattice_vectors[1] + lattice_vectors[2]
- center).tolist(),
"xyz": (lattice_vectors[0] + lattice_vectors[1]
+ lattice_vectors[2] - center).tolist(),
}

# These will be passed to ChemDoodle
response["str_viz_info"]["data"] = {"s": [cell_json],
"m": [{"a": atoms_json}],
"units": '&Aring;'
}
response["str_viz_info"]["format"] = "default (ChemDoodle)"
raise RestInputValidationError("The format {} is not supported.".format(format))

# Add extra information
response["dimensionality"] = node.get_dimensionality()
Expand Down