diff --git a/aiida/backends/tests/restapi.py b/aiida/backends/tests/restapi.py index eccd6aa543..2e46135674 100644 --- a/aiida/backends/tests/restapi.py +++ b/aiida/backends/tests/restapi.py @@ -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 diff --git a/aiida/orm/data/structure.py b/aiida/orm/data/structure.py index d3c2e2dde6..d9a810ae9a 100644 --- a/aiida/orm/data/structure.py +++ b/aiida/orm/data/structure.py @@ -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 + Ge0.5 + Si0.4 + vacancy0.1 + + 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 + '' + weight + + '') + 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 @@ -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": 'Å' + } + + return json.dumps(return_dict), {} + def _prepare_xyz(self, main_file_name=""): """ Write the given structure to a string of format XYZ. diff --git a/aiida/restapi/translator/data/structure.py b/aiida/restapi/translator/data/structure.py index 210fd1982e..9a5a394414 100644 --- a/aiida/restapi/translator/data/structure.py +++ b/aiida/restapi/translator/data/structure.py @@ -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 - Ge0.5 + Si0.4 + vacancy0.1 - - 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 + '' + weight + - '') - 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 @@ -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"] = {} + 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": 'Å' - } - response["str_viz_info"]["format"] = "default (ChemDoodle)" + raise RestInputValidationError("The format {} is not supported.".format(format)) # Add extra information response["dimensionality"] = node.get_dimensionality()