Пример #1
0
    def test_aiida_roundtrip(self):
        cell = np.array([[4., 1., 0.], [0., 4., 0.], [0., 0., 4.]])

        struc = StructureData(cell=cell)
        struc.append_atom(symbols='Ba', position=(0, 0, 0))
        struc.append_atom(symbols='Ti', position=(1, 2, 3))
        struc.append_atom(symbols='O', position=(-1, -2, -4))
        struc.append_kind(Kind(name='Ba2', symbols="Ba", mass=100.))
        struc.append_site(Site(kind_name='Ba2', position=[3, 2, 1]))
        struc.append_kind(
            Kind(name='Test',
                 symbols=["Ba", "Ti"],
                 weights=[0.2, 0.4],
                 mass=120.))
        struc.append_site(Site(kind_name='Test', position=[3, 5, 1]))

        struc_tuple, kind_info, kinds = seekpath.aiidawrappers._aiida_to_tuple(
            struc)
        roundtrip_struc = seekpath.aiidawrappers._tuple_to_aiida(
            struc_tuple, kind_info, kinds)

        self.assertAlmostEqual(
            np.sum(
                np.abs(np.array(struc.cell) - np.array(roundtrip_struc.cell))),
            0.)
        self.assertEqual(struc.get_attr('kinds'),
                         roundtrip_struc.get_attr('kinds'))
        self.assertEqual([_.kind_name for _ in struc.sites],
                         [_.kind_name for _ in roundtrip_struc.sites])
        self.assertEqual(
            np.sum(
                np.abs(
                    np.array([_.position for _ in struc.sites]) -
                    np.array([_.position for _ in roundtrip_struc.sites]))),
            0.)
Пример #2
0
    def test_from_aiida(self):
        cell = np.array([[4., 1., 0.], [0., 4., 0.], [0., 0., 4.]])

        struc = StructureData(cell=cell)
        struc.append_atom(symbols='Ba', position=(0, 0, 0))
        struc.append_atom(symbols='Ti', position=(1, 2, 3))
        struc.append_atom(symbols='O', position=(-1, -2, -4))
        struc.append_kind(Kind(name='Ba2', symbols="Ba", mass=100.))
        struc.append_site(Site(kind_name='Ba2', position=[3, 2, 1]))
        struc.append_kind(
            Kind(name='Test',
                 symbols=["Ba", "Ti"],
                 weights=[0.2, 0.4],
                 mass=120.))
        struc.append_site(Site(kind_name='Test', position=[3, 5, 1]))

        struc_tuple, kind_info, kinds = seekpath.aiidawrappers._aiida_to_tuple(
            struc)

        abscoords = np.array([_.position for _ in struc.sites])
        struc_relpos = np.dot(np.linalg.inv(cell.T), abscoords.T).T

        self.assertAlmostEqual(
            np.sum(np.abs(np.array(struc.cell) - np.array(struc_tuple[0]))),
            0.)
        self.assertAlmostEqual(
            np.sum(np.abs(np.array(struc_tuple[1]) - struc_relpos)), 0.)

        expected_kind_info = [
            kind_info[site.kind_name] for site in struc.sites
        ]
        self.assertEqual(struc_tuple[2], expected_kind_info)
Пример #3
0
def move_atoms_incell(structure, vector):
    """
    moves all atoms in a unit cell by a given vector

    para: AiiDA structure
    para: vector: tuple of 3, or array

    returns: AiiDA stucture
    """

    StructureData = DataFactory('structure')
    new_structure = StructureData(cell=structure.cell)
    new_structure.pbc = structure.pbc
    sites = structure.sites
    for kind in structure.kinds:
        new_structure.append_kind(kind)

    for site in sites:
        pos = site.position
        new_pos = np.array(pos) + np.array(vector)
        new_site = Site(kind_name=site.kind_name, position=new_pos)
        new_structure.append_site(new_site)
        new_structure.label = structure.label

    new_structure.label = structure.label
    new_structure.description = structure.description + 'moved'
    return new_structure
Пример #4
0
    def get_step_structure(self, index, custom_kinds=None):
        """
        Return an AiiDA :py:class:`aiida.orm.data.structure.StructureData` node
        (not stored yet!) with the coordinates of the given step, identified by
        its index. If you know only the step value, use the
        :py:meth:`.get_index_from_stepid` method to get the corresponding index.

        .. note:: The periodic boundary conditions are always set to True.

        .. versionadded:: 0.7
           Renamed from step_to_structure

        :param index: The index of the step that you want to retrieve, from
           0 to ``self.numsteps- 1``.
        :param custom_kinds: (Optional) If passed must be a list of
          :py:class:`aiida.orm.data.structure.Kind` objects. There must be one
          kind object for each different string in the ``symbols`` array, with
          ``kind.name`` set to this string.
          If this parameter is omitted, the automatic kind generation of AiiDA
          :py:class:`aiida.orm.data.structure.StructureData` nodes is used,
          meaning that the strings in the ``symbols`` array must be valid
          chemical symbols.
        """
        from aiida.orm.data.structure import StructureData, Kind, Site

        # ignore step, time, and velocities
        _, _, cell, symbols, positions, _ = self.get_step_data(index)

        if custom_kinds is not None:
            kind_names = []
            for k in custom_kinds:
                if not isinstance(k, Kind):
                    raise TypeError(
                        "Each element of the custom_kinds list must "
                        "be a aiida.orm.data.structure.Kind object")
                kind_names.append(k.name)
            if len(kind_names) != len(set(kind_names)):
                raise ValueError("Multiple kinds with the same name passed "
                                 "as custom_kinds")
            if set(kind_names) != set(symbols):
                raise ValueError("If you pass custom_kinds, you have to "
                                 "pass one Kind object for each symbol "
                                 "that is present in the trajectory. You "
                                 "passed {}, but the symbols are {}".format(
                                     sorted(kind_names), sorted(symbols)))

        struc = StructureData(cell=cell)
        if custom_kinds is not None:
            for k in custom_kinds:
                struc.append_kind(k)
            for s, p in zip(symbols, positions):
                struc.append_site(Site(kind_name=s, position=p))
        else:
            for s, p in zip(symbols, positions):
                # Automatic species generation
                struc.append_atom(symbols=s, position=p)

        return struc
Пример #5
0
def dict_to_structure(structdict, logger=None):
    """create a dictionary of structure properties per atom

    :param: dictionary containing; 'lattice', 'atomic_numbers' (or 'symbols'), 'ccoords', 'pbc', 'kinds', 'equivalent'
    :type structdict: dict
    :param logger: a logger with a `warning` method
    :return structure: the input structure
    :rtype structure: aiida.orm.data.structure.StructureData

    """
    from aiida.orm import DataFactory
    StructureData = DataFactory('structure')
    struct = StructureData(cell=structdict['lattice'])
    struct.set_pbc(structdict["pbc"])
    atom_kinds = structdict.get("kinds", None)

    if atom_kinds is None:
        if logger:
            logger.warning("no 'kinds' available, creating new kinds")
        symbols = structdict.get("symbols", None)
        if symbols is None:
            symbols = [
                ATOMIC_NUM2SYMBOL[anum]
                for anum in structdict["atomic_numbers"]
            ]

        if len(symbols) != len(structdict['ccoords']):
            raise AssertionError(
                "the length of ccoords and atomic_numbers/symbols must be the same"
            )
        for symbol, ccoord in zip(symbols, structdict['ccoords']):
            struct.append_atom(position=ccoord, symbols=symbol)
    else:
        if len(atom_kinds) != len(structdict['ccoords']):
            raise AssertionError(
                "the length of ccoords and atom_kinds must be the same")

        from aiida.orm.data.structure import Site, Kind
        for kind, ccoord in zip(atom_kinds, structdict['ccoords']):
            if not isinstance(kind, Kind):
                kind = Kind(raw=kind)
            if kind.name not in struct.get_site_kindnames():
                struct.append_kind(kind)
            struct.append_site(Site(position=ccoord, kind_name=kind.name))

    return struct
Пример #6
0
    def get_structuredata(self):
        """
        Return a StructureData object based on the data in the input file.
        
        This uses all of the data in the input file to do the necessary unit 
        conversion, ect. and then creates an AiiDA StructureData object.
    
        All of the names corresponding of the Kind objects composing the 
        StructureData object will match those found in the ATOMIC_SPECIES 
        block, so the pseudopotentials can be linked to the calculation using 
        the kind.name for each specific type of atom (in the event that you 
        wish to use different pseudo's for two or more of the same atom).
    
        :return: StructureData object of the structure in the input file
        :rtype: aiida.orm.data.structure.StructureData
        :raises aiida.common.exceptions.ParsingError: if there are issues
            parsing the input.
        """
        from aiida.orm.data.structure import StructureData, Kind, Site

        valid_elements_regex = re.compile(
            """
            (?P<ele>
H  | He |
Li | Be | B  | C  | N  | O  | F  | Ne |
Na | Mg | Al | Si | P  | S  | Cl | Ar |
K  | Ca | Sc | Ti | V  | Cr | Mn | Fe | Co | Ni | Cu | Zn | Ga | Ge | As | Se | Br | Kr |
Rb | Sr | Y  | Zr | Nb | Mo | Tc | Ru | Rh | Pd | Ag | Cd | In | Sn | Sb | Te | I  | Xe |
Cs | Ba | Hf | Ta | W  | Re | Os | Ir | Pt | Au | Hg | Tl | Pb | Bi | Po | At | Rn |
Fr | Ra | Rf | Db | Sg | Bh | Hs | Mt |

La | Ce | Pr | Nd | Pm | Sm | Eu | Gd | Tb | Dy | Ho | Er | Tm | Yb | Lu | # Lanthanides
Ac | Th | Pa | U  | Np | Pu | Am | Cm | Bk | Cf | Es | Fm | Md | No | Lr | # Actinides
        )
        [^a-z]  # Any specification of an element is followed by some number
                # or capital letter or special character.
""", re.X | re.I)

        structure_dict = self.get_structure_from_qeinput()
        # instance and set the cell
        structuredata = StructureData()
        structuredata._set_attr('cell', structure_dict['cell'].tolist())

        #################  KINDS ##########################
        for mass, name, pseudo in zip(
                structure_dict['species']['masses'],
                structure_dict['species']['names'],
                structure_dict['species']['pseudo_file_names']):
            try:
                # IMPORTANT: The symbols is parsed from the Pseudo file name
                # Is this the best way??
                # Should we also try from the associated kind name?
                symbols = valid_elements_regex.search(pseudo).group(
                    'ele').capitalize()
            except Exception as e:
                raise InputValidationError(
                    'I could not read an element name in {}'.format(
                        match.group(0)))
            structuredata.append_kind(
                Kind(
                    name=name,
                    symbols=symbols,
                    mass=mass,
                ))

        [
            structuredata.append_site(Site(
                kind_name=sym,
                position=pos,
            )) for sym, pos in zip(structure_dict['atom_names'],
                                   structure_dict['positions'])
        ]

        return structuredata
Пример #7
0
def _tuple_to_aiida(structure_tuple, kind_info=None, kinds=None):
    """
    Convert an tuple of the format
    (cell, scaled_positions, element_numbers) to an AiiDA structure.

    Unless the element_numbers are identical to the Z number of the atoms,
    you should pass both kind_info and kinds, with the same format as returned
    by get_tuple_from_aiida_structure.

    .. deprecated:: 1.8
      Use the methods in AiiDA instead.

    :param structure_tuple: the structure in format (structure_tuple, kind_info)
    :param kind_info: a dictionary mapping the kind_names to
       the numbers used in element_numbers. If not provided, assumes {element_name: element_Z}
    :param kinds: a list of the kinds of the structure.
    """
    import warnings
    warnings.warn(
        'this method has been deprecated and moved to AiiDA, see {}'.format(
            DEPRECATION_DOCS_URL), DeprecationWarning)

    from aiida.common.constants import elements
    from aiida.orm.data.structure import Kind, Site, StructureData
    import numpy as np
    import copy

    if kind_info is None and kinds is not None:
        raise ValueError("If you pass kind_info, you should also pass kinds")
    if kinds is None and kind_info is not None:
        raise ValueError("If you pass kinds, you should also pass kind_info")

    Z = {v['symbol']: k for k, v in elements.items()}
    cell, rel_pos, numbers = structure_tuple
    if kind_info:
        _kind_info = copy.copy(kind_info)
        _kinds = copy.copy(kinds)
    else:
        try:
            # For each site
            symbols = [elements[num]['symbol'] for num in numbers]
        except KeyError as e:
            raise ValueError(
                "You did not pass kind_info, but at least one number "
                "is not a valid Z number: {}".format(e.message))

        _kind_info = {elements[num]['symbol']: num for num in set(numbers)}
        # Get the default kinds
        _kinds = [Kind(symbols=sym) for sym in set(symbols)]

    _kinds_dict = {k.name: k for k in _kinds}
    # Now I will use in any case _kinds and _kind_info
    if len(_kind_info.values()) != len(set(_kind_info.values())):
        raise ValueError(
            "There is at least a number repeated twice in kind_info!")
    # Invert the mapping
    mapping_num_kindname = {v: k for k, v in _kind_info.items()}
    # Create the actual mapping
    try:
        mapping_to_kinds = {
            num: _kinds_dict[kindname]
            for num, kindname in mapping_num_kindname.items()
        }
    except KeyError as e:
        raise ValueError("Unable to find '{}' in the kinds list".format(
            e.message))

    try:
        site_kinds = [mapping_to_kinds[num] for num in numbers]
    except KeyError as e:
        raise ValueError(
            "Unable to find kind in kind_info for number {}".format(e.message))

    out_structure = StructureData(cell=cell)
    for k in _kinds:
        out_structure.append_kind(k)
    abs_pos = np.dot(rel_pos, cell)
    if len(abs_pos) != len(site_kinds):
        raise ValueError(
            "The length of the positions array is different from the "
            "length of the element numbers")

    for kind, pos in zip(site_kinds, abs_pos):
        out_structure.append_site(Site(kind_name=kind.name, position=pos))

    return out_structure
Пример #8
0
def get_structuredata_from_qeinput(filepath=None,
                                   text=None,
                                   namelists=None,
                                   atomic_species=None,
                                   atomic_positions=None,
                                   cell_parameters=None):
    """
    Function that receives either
    :param filepath: the filepath storing **or**
    :param text: the string of a standard QE-input file.
    An instance of :func:`StructureData` is initialized with kinds, positions and cell
    as defined in the input file.
    This function can deal with ibrav being set different from 0 and the cell being defined
    with celldm(n) or A,B,C, cosAB etc.
    """
    from aiida.orm.data.structure import StructureData, Kind, Site
    #~ from aiida.common.utils import get_fortfloat

    valid_elements_regex = re.compile(
        """
        (?P<ele>
H  | He |
Li | Be | B  | C  | N  | O  | F  | Ne |
Na | Mg | Al | Si | P  | S  | Cl | Ar |
K  | Ca | Sc | Ti | V  | Cr | Mn | Fe | Co | Ni | Cu | Zn | Ga | Ge | As | Se | Br | Kr |
Rb | Sr | Y  | Zr | Nb | Mo | Tc | Ru | Rh | Pd | Ag | Cd | In | Sn | Sb | Te | I  | Xe |
Cs | Ba | Hf | Ta | W  | Re | Os | Ir | Pt | Au | Hg | Tl | Pb | Bi | Po | At | Rn |
Fr | Ra | Rf | Db | Sg | Bh | Hs | Mt |

La | Ce | Pr | Nd | Pm | Sm | Eu | Gd | Tb | Dy | Ho | Er | Tm | Yb | Lu | # Lanthanides
Ac | Th | Pa | U  | Np | Pu | Am | Cm | Bk | Cf | Es | Fm | Md | No | Lr | # Actinides
        )
        [^a-z]  # Any specification of an element is followed by some number
                # or capital letter or special character.
    """, re.X | re.I)
    # I need either a valid filepath or the text of the qeinput file:
    if filepath:
        with open(filepath) as f:
            txt = f.read()
    elif text:
        txt = text
    else:
        raise InputValidationError(
            'Provide either a filepath or text to be parsed')

    if namelists is None:
        namelists = parse_namelists(text)
    if atomic_species is None:
        atomic_species = parse_atomic_species(txt)
    if cell_parameters is None:
        cell_parameters = parse_cell_parameters(txt)
    if atomic_positions is None:
        atomic_positions = parse_atomic_positions(txt)

    # First, I'm trying to figure out whether alat was specified:
    system_dict = namelists['SYSTEM']

    if 'a' in system_dict and 'celldm(1)' in system_dict:
        # The user should define exclusively in celldm or ABC-system
        raise InputValidationError('Both a and celldm(1) specified')
    elif 'a' in system_dict:
        alat = system_dict['a']
        using_celldm = False
    elif 'celldm(1)' in system_dict:
        alat = bohr_to_ang * system_dict['celldm(1)']
        using_celldm = True
    else:
        alat = None
        using_celldm = None

    cell = get_cell_from_parameters(cell_parameters, system_dict, alat,
                                    using_celldm)

    # instance and set the cell
    structuredata = StructureData()
    structuredata._set_attr('cell', cell.tolist())

    #################  KINDS ##########################

    for mass, name, pseudo in zip(atomic_species['masses'],
                                  atomic_species['names'],
                                  atomic_species['pseudo_file_names']):
        try:
            symbols = valid_elements_regex.search(pseudo).group(
                'ele').capitalize()
        except Exception as e:
            raise InputValidationError(
                'I could not read an element name in {}'.format(
                    match.group(0)))
        structuredata.append_kind(Kind(
            name=name,
            symbols=symbols,
            mass=mass,
        ))

    ################## POSITIONS #######################
    positions_units = atomic_positions['units']
    positions = np.array(atomic_positions['positions'])

    if positions_units is None:
        raise InputValidationError(
            "There is no unit for positions\n"
            "This is deprecated behavior for QE.\n"
            "In addition the default values by CP and PW differ (bohr and alat)"
        )
    elif positions_units == 'angstrom':
        pass
    elif positions_units == 'bohr':
        positions = bohr_to_ang * positions
    elif positions_units == 'crystal':
        positions = np.dot(positions, cell)
    elif positions_units == 'alat':
        positions = np.linalg.norm(cell[0]) * positions
    elif positions_units == 'crystal_sg':
        raise NotImplementedError('crystal_sg is not implemented')
    else:
        valid_positions_units = ('alat', 'bohr', 'angstrom', 'crystal',
                                 'crystal_sg')
        raise InputValidationError('\nFound atom unit {}, which is not\n'
                                   'among the valid units: {}'.format(
                                       positions_units,
                                       ', '.join(valid_positions_units)))
    ######### DEFINE SITES ######################

    positions = positions.tolist()
    [
        structuredata.append_site(Site(
            kind_name=sym,
            position=pos,
        )) for sym, pos in zip(atomic_positions['names'], positions)
    ]
    return structuredata
Пример #9
0
def break_symmetry(structure,
                   atoms=['all'],
                   site=[],
                   pos=[],
                   new_kinds_names={},
                   parameterData=None):
    """
    This routine introduces different 'kind objects' in a structure
    and names them that inpgen will make different species/atomgroups out of them.
    If nothing specified breaks ALL symmetry (i.e. every atom gets their own kind)

    params: StructureData
    params: atoms: python list of symbols, exp: ['W', 'Be']. This would make for
                   all Be and W atoms their own kinds.
    params: site: python list of integers, exp: [1, 4, 8]. This would create for
                  atom 1, 4 and 8 their own kinds.
    params: pos: python list of tuples of 3, exp [(0.0, 0.0, -1.837927), ...].
                 This will create a new kind for the atom at that position.
                 Be carefull the number given has to match EXACTLY the position
                 in the structure.

    return: StructureData, a AiiDA crystal structure with new kind specification.
    """
    # TODO proper input checks?
    from aiida.common.constants import elements as PeriodicTableElements

    _atomic_numbers = {
        data['symbol']: num
        for num, data in PeriodicTableElements.iteritems()
    }

    #get all atoms, get the symbol of the atom
    #if wanted make individual kind for that atom
    #kind names will be atomsymbol+number
    #create new structure with new kinds and atoms
    #Param = DataFactory('parameter')
    symbol_count = {
    }  # Counts the atom symbol occurence to set id's and kind names right
    replace = []  # all atoms symbols ('W') to be replaced
    replace_siteN = []  # all site integers to be replaced
    replace_pos = []  #all the atom positions to be replaced
    new_parameterd = None
    struc = is_structure(structure)
    if not struc:
        print 'Error, no structure given'
        # throw error?

    cell = struc.cell
    pbc = struc.pbc
    sites = struc.sites
    #natoms = len(sites)
    new_structure = DataFactory('structure')(cell=cell, pbc=pbc)

    for sym in atoms:
        replace.append(sym)
    for position in pos:
        replace_pos.append(position)
    for atom in site:
        replace_siteN.append(atom)

    if parameterData:
        para = parameterData.get_dict()
        new_parameterd = dict(para)
    else:
        new_parameterd = {}

    for i, site in enumerate(sites):
        kind_name = site.kind_name
        pos = site.position
        kind = struc.get_kind(kind_name)
        symbol = kind.symbol
        replace_kind = False

        if symbol in replace or 'all' in replace:
            replace_kind = True
        if pos in replace_pos:
            replace_kind = True
        if i in replace_siteN:
            replace_kind = True

        if replace_kind:
            if symbol in symbol_count:
                symbol_count[symbol] = symbol_count[symbol] + 1
                symbol_new_kinds_names = new_kinds_names.get(symbol, [])
                print(symbol_new_kinds_names)
                if symbol_new_kinds_names and ((len(symbol_new_kinds_names))
                                               == symbol_count[symbol]):
                    newkindname = symbol_new_kinds_names[symbol_count[symbol] -
                                                         1]
                else:
                    newkindname = '{}{}'.format(symbol, symbol_count[symbol])
            else:
                symbol_count[symbol] = 1
                symbol_new_kinds_names = new_kinds_names.get(symbol, [])
                #print(symbol_new_kinds_names)
                if symbol_new_kinds_names and ((len(symbol_new_kinds_names))
                                               == symbol_count[symbol]):
                    newkindname = symbol_new_kinds_names[symbol_count[symbol] -
                                                         1]
                else:
                    newkindname = '{}{}'.format(symbol, symbol_count[symbol])
            #print(newkindname)
            new_kind = Kind(name=newkindname, symbols=symbol)
            new_structure.append_kind(new_kind)

            # now we have to add an atom list to parameterData with the corresponding id.
            if parameterData:
                id_a = symbol_count[
                    symbol]  #'{}.{}'.format(charge, symbol_count[symbol])
                #print 'id: {}'.format(id)
                for key, val in para.iteritems():
                    if 'atom' in key:
                        if val.get('element', None) == symbol:
                            if id_a and id_a == val.get('id', None):
                                break  # we assume the user is smart and provides a para node,
                                # which incooperates the symmetry breaking already
                            elif id_a:  # != 1: # copy parameter of symbol and add id
                                val_new = dict(val)
                                # getting the charge over element might be risky
                                charge = _atomic_numbers.get(
                                    (val.get('element')))
                                idp = '{}.{}'.format(charge,
                                                     symbol_count[symbol])
                                idp = float("{0:.2f}".format(float(idp)))
                                # dot cannot be stored in AiiDA dict...
                                val_new.update({u'id': idp})
                                atomlistname = 'atom{}'.format(id_a)
                                i = 0
                                while new_parameterd.get(atomlistname, {}):
                                    i = i + 1
                                    atomlistname = 'atom{}'.format(id_a + i)

                                symbol_new_kinds_names = new_kinds_names.get(
                                    symbol, [])
                                #print(symbol_new_kinds_names)
                                if symbol_new_kinds_names and (
                                    (len(symbol_new_kinds_names))
                                        == symbol_count[symbol]):
                                    species_name = symbol_new_kinds_names[
                                        symbol_count[symbol] - 1]
                                val_new.update({u'name': species_name})

                                new_parameterd[atomlistname] = val_new
            else:
                pass
                #TODO write basic parameter data node
        else:
            newkindname = kind_name
            if not kind_name in new_structure.get_kind_names():
                new_structure.append_kind(kind)
        new_structure.append_site(Site(kind_name=newkindname, position=pos))

    #print 'natoms: {}, nkinds: {}'.format(natoms, len(new_structure.get_kind_names()))

    if parameterData:
        para_new = ParameterData(dict=new_parameterd)
    else:
        para_new = None

    new_structure.label = structure.label
    new_structure.description = structure.description + 'more kinds, less sym'

    return new_structure, para_new
Пример #10
0
def supercell_nwf(
    inp_structure, n_a1, n_a2, n_a3
):  #, _label=u'supercell_wf', _description=u'WF, Creates a supercell of a crystal structure x(n1,n2,n3).'):# be carefull you have to use AiiDA datatypes...
    """
    Creates a super cell from a StructureData node.
    Does NOT keeps the provanance in the database.

    :param StructureData, a StructureData node (pk, or uuid)
    :param scale: tuple of 3 AiiDA integers, number of cells in a1, a2, a3, or if cart =True in x,y,z

    :returns StructureData, Node with supercell
    """
    #print('in create supercell')
    #test if structure:
    structure = is_structure(inp_structure)
    if not structure:
        #TODO: log something
        return None
    old_cell = structure.cell
    old_a1 = old_cell[0]
    old_a2 = old_cell[1]
    old_a3 = old_cell[2]
    old_sites = structure.sites
    old_pbc = structure.pbc

    na1 = int(n_a1)
    na2 = int(n_a2)
    na3 = int(n_a3)

    #new cell
    new_a1 = [i * na1 for i in old_a1]
    new_a2 = [i * na2 for i in old_a2]
    new_a3 = [i * na3 for i in old_a3]
    new_cell = [new_a1, new_a2, new_a3]
    new_structure = DataFactory('structure')(cell=new_cell, pbc=old_pbc)

    #insert atoms
    # first create all kinds
    old_kinds = structure.kinds
    for kind in old_kinds:
        new_structure.append_kind(kind)

    #scale n_a1
    for site in old_sites:
        # get atom position
        kn = site.kind_name
        pos_o = site.position
        for j in range(na1):
            pos = [pos_o[i] + j * old_a1[i] for i in range(0, len(old_a1))]
            new_structure.append_site(Site(kind_name=kn, position=pos))

    #scale n_a2
    o_sites = new_structure.sites
    for site in o_sites:
        # get atom position
        kn = site.kind_name
        pos_o = site.position
        for j in range(1, na2):  # j=0 these sites/atoms are already added
            pos = [pos_o[i] + j * old_a2[i] for i in range(0, len(old_a2))]
            new_structure.append_site(Site(kind_name=kn, position=pos))

    #scale n_a3
    o_sites = new_structure.sites
    for site in o_sites:
        # get atom position
        kn = site.kind_name
        pos_o = site.position
        for j in range(1, na3):  # these sites/atoms are already added
            pos = [pos_o[i] + j * old_a3[i] for i in range(0, len(old_a3))]
            new_structure.append_site(Site(kind_name=kn, position=pos))

    new_structure.label = 'supercell of {}'.format(formula)
    new_structure.description = '{}x{}x{} supercell of {}'.format(
        n_a1, n_a2, n_a3, inp_structure.get_formula())
    return new_structure