Ejemplo n.º 1
0
Archivo: castep.py Proyecto: lqcata/ase
def read_cell(filename, _=None):
    """Read a .cell file and return an atoms object.
    Any value found that does not fit the atoms API
    will be stored in the atoms.calc attribute.
    """

    from ase.calculators.castep import Castep
    calc = Castep()

    fileobj = open(filename)
    lines = fileobj.readlines()
    fileobj.close()

    def get_tokens(lines, l):
        """Tokenizes one line of a *cell file."""
        comment_chars = "#!"
        while l < len(lines):
            line = lines[l].strip()
            if len(line) == 0:
                l += 1
                continue
            elif any([
                    line.startswith(comment_char)
                    for comment_char in comment_chars
            ]):
                l += 1
                continue
            else:
                for c in comment_chars:
                    if c in line:
                        icomment = min(line.index(c))
                    else:
                        icomment = len(line)
                tokens = line[:icomment].split()
                return tokens, l + 1
        tokens = ""
        print("read_cell: Warning - get_tokens has not found any more tokens")
        return tokens, l

    lat = []
    have_lat = False

    pos = []
    spec = []
    constraints = []
    raw_constraints = {}
    have_pos = False
    pos_frac = False

    l = 0
    while l < len(lines):
        tokens, l = get_tokens(lines, l)
        if not tokens:
            continue
        elif tokens[0].upper() == "%BLOCK":
            if tokens[1].upper() == "LATTICE_CART" and not have_lat:
                tokens, l = get_tokens(lines, l)
                if len(tokens) == 1:
                    print('read_cell: Warning - ignoring unit specifier in')
                    print('%BLOCK LATTICE_CART (assuming Angstrom instead)')
                    tokens, l = get_tokens(lines, l)
                for _ in range(3):
                    lat_vec = map(float, tokens[0:3])
                    lat.append(lat_vec)
                    tokens, l = get_tokens(lines, l)
                if tokens[0].upper() != "%ENDBLOCK":
                    print('read_cell: Warning - ignoring more than three')
                    print('lattice vectors in invalid %BLOCK LATTICE_CART')
                    print('%s ...' % tokens[0].upper())
                have_lat = True

            elif tokens[1].upper() == "LATTICE_ABC" and not have_lat:
                tokens, l = get_tokens(lines, l)
                if len(tokens) == 1:
                    print('read_cell: Warning - ignoring unit specifier in')
                    print('%BLOCK LATTICE_ABC (assuming Angstrom instead)')
                    tokens, l = get_tokens(lines, l)
                a, b, c = map(float, tokens[0:3])
                tokens, l = get_tokens(lines, l)
                alpha, beta, gamma = map(lambda phi: radians(float(phi)),
                                         tokens[0:3])
                tokens, l = get_tokens(lines, l)
                if tokens[0].upper() != "%ENDBLOCK":
                    print('read_cell: Warning - ignoring additional lines in')
                    print('invalid %BLOCK LATTICE_ABC')
                lat_a = [a, 0, 0]
                lat_b = [b * cos(gamma), b * sin(gamma), 0]
                lat_c1 = c * cos(beta)
                lat_c2 = c * (cos(alpha) - cos(beta) * cos(gamma)) / sin(gamma)
                lat_c3 = sqrt(c * c - lat_c1 * lat_c1 - lat_c2 * lat_c2)
                lat_c = [lat_c1, lat_c2, lat_c3]
                lat = [lat_a, lat_b, lat_c]
                have_lat = True

            elif tokens[1].upper() == "POSITIONS_ABS" and not have_pos:
                tokens, l = get_tokens(lines, l)
                if len(tokens) == 1:
                    print('read_cell: Warning - ignoring unit specifier in')
                    print('%BLOCK POSITIONS_ABS(assuming Angstrom instead)')
                    tokens, l = get_tokens(lines, l)
                while len(tokens) == 4:
                    spec.append(tokens[0])
                    pos.append(map(float, tokens[1:4]))
                    tokens, l = get_tokens(lines, l)
                if tokens[0].upper() != "%ENDBLOCK":
                    print('read_cell: Warning - ignoring invalid lines in')
                    print('%%BLOCK POSITIONS_ABS:\n\t %s' % tokens)
                have_pos = True

            elif tokens[1].upper() == "POSITIONS_FRAC" and not have_pos:
                pos_frac = True
                tokens, l = get_tokens(lines, l)
                while len(tokens) == 4:
                    spec.append(tokens[0])
                    pos.append(map(float, tokens[1:4]))
                    tokens, l = get_tokens(lines, l)
                if tokens[0].upper() != "%ENDBLOCK":
                    print('read_cell: Warning - ignoring invalid lines')
                    print('%%BLOCK POSITIONS_FRAC:\n\t %s' % tokens)
                have_pos = True
            elif tokens[1].upper() == 'SPECIES_POT':
                tokens, l = get_tokens(lines, l)
                while tokens and not tokens[0].upper() == '%ENDBLOCK':
                    if len(tokens) == 2:
                        calc.cell.species_pot = tuple(tokens)
                    tokens, l = get_tokens(lines, l)
            elif tokens[1].upper() == 'IONIC_CONSTRAINTS':

                while True:
                    if tokens and tokens[0].upper() == '%ENDBLOCK':
                        break
                    tokens, l = get_tokens(lines, l)
                    if not len(tokens) == 6:
                        continue
                    _, species, nic, x, y, z = tokens
                    nic = int(nic)
                    if (species, nic) not in raw_constraints:
                        raw_constraints[(species, nic)] = []
                    raw_constraints[(species, nic)].append(array([x, y, z]))
            else:
                print('Warning: the keyword %s is not' % tokens[1].upper())
                print('         interpreted in cell files')
                while not tokens[0].upper() == '%ENDBLOCK':
                    tokens, l = get_tokens(lines, l)
                #raise UserWarning
        else:
            key = tokens[0]
            value = ' '.join(tokens[1:])
            try:
                calc.__setattr__(key, value)
            except:
                print("Problem setting calc.cell.%s = %s" % (key, value))
                raise

    if pos_frac:
        atoms = ase.Atoms(
            calculator=calc,
            cell=lat,
            pbc=True,
            scaled_positions=pos,
            symbols=spec,
        )
    else:
        atoms = ase.Atoms(
            calculator=calc,
            cell=lat,
            pbc=True,
            positions=pos,
            symbols=spec,
        )

    fixed_atoms = []
    for (species, nic), value in raw_constraints.iteritems():
        absolute_nr = atoms.calc._get_absolute_number(species, nic)
        if len(value) == 3:
            fixed_atoms.append(absolute_nr)
        elif len(value) == 2:
            constraint = ase.constraints.FixedLine(a=absolute_nr,
                                                   direction=cross(
                                                       value[0], value[1]))
            constraints.append(constraint)
        elif len(value) == 1:
            constraint = ase.constraints.FixedPlane(a=absolute_nr,
                                                    direction=array(
                                                        value[0],
                                                        dtype=float32))
            constraints.append(constraint)
        else:
            print('Error: Found %s statements attached to atoms %s' %
                  (len(value), absolute_nr))
    constraints.append(ase.constraints.FixAtoms(fixed_atoms))
    atoms.set_constraint(constraints)

    # needs to go here again to have the constraints in
    # atoms.calc.atoms.constraints as well
    atoms.calc.atoms = atoms
    atoms.calc.push_oldstate()
    return atoms
Ejemplo n.º 2
0
def read_castep_cell(filename, _=None):
    """Read a .cell file and return an atoms object.
    Any value found that does not fit the atoms API
    will be stored in the atoms.calc attribute.
    """

    from ase.calculators.castep import Castep
    calc = Castep()

    fileobj = open(filename)
    lines = fileobj.readlines()
    fileobj.close()

    def get_tokens(lines, l):
        """Tokenizes one line of a *cell file."""
        comment_chars = "#!"
        while l < len(lines):
            line = lines[l].strip()
            if len(line) == 0:
                l += 1
                continue
            elif any([line.startswith(comment_char)
                      for comment_char in comment_chars]):
                l += 1
                continue
            else:
                for c in comment_chars:
                    if c in line:
                        icomment = min(line.index(c))
                    else:
                        icomment = len(line)
                tokens = line[:icomment].split()
                return tokens, l + 1
        tokens = ""
        print("read_cell: Warning - get_tokens has not found any more tokens")
        return tokens, l

    lat = []
    have_lat = False

    pos = []
    spec = []
    constraints = []
    raw_constraints = {}
    have_pos = False
    pos_frac = False

    l = 0
    while l < len(lines):
        tokens, l = get_tokens(lines, l)
        if not tokens:
            continue
        elif tokens[0].upper() == "%BLOCK":
            if tokens[1].upper() == "LATTICE_CART" and not have_lat:
                tokens, l = get_tokens(lines, l)
                if len(tokens) == 1:
                    print('read_cell: Warning - ignoring unit specifier in')
                    print('%BLOCK LATTICE_CART (assuming Angstrom instead)')
                    tokens, l = get_tokens(lines, l)
                for _ in range(3):
                    lat_vec = [float(a) for a in tokens[0:3]]
                    lat.append(lat_vec)
                    tokens, l = get_tokens(lines, l)
                if tokens[0].upper() != "%ENDBLOCK":
                    print('read_cell: Warning - ignoring more than three')
                    print('lattice vectors in invalid %BLOCK LATTICE_CART')
                    print('%s ...' % tokens[0].upper())
                have_lat = True

            elif tokens[1].upper() == "LATTICE_ABC" and not have_lat:
                tokens, l = get_tokens(lines, l)
                if len(tokens) == 1:
                    print('read_cell: Warning - ignoring unit specifier in')
                    print('%BLOCK LATTICE_ABC (assuming Angstrom instead)')
                    tokens, l = get_tokens(lines, l)
                a, b, c = map(float, tokens[0:3])
                tokens, l = get_tokens(lines, l)
                alpha, beta, gamma = [radians(float(phi)) for phi in tokens[0:3]]
                tokens, l = get_tokens(lines, l)
                if tokens[0].upper() != "%ENDBLOCK":
                    print('read_cell: Warning - ignoring additional lines in')
                    print('invalid %BLOCK LATTICE_ABC')
                lat_a = [a, 0, 0]
                lat_b = [b * cos(gamma), b * sin(gamma), 0]
                lat_c1 = c * cos(beta)
                lat_c2 = c * (cos(alpha) - cos(beta) * cos(gamma)) / sin(gamma)
                lat_c3 = sqrt(c * c - lat_c1 * lat_c1 - lat_c2 * lat_c2)
                lat_c = [lat_c1, lat_c2, lat_c3]
                lat = [lat_a, lat_b, lat_c]
                have_lat = True

            elif tokens[1].upper() == "POSITIONS_ABS" and not have_pos:
                tokens, l = get_tokens(lines, l)
                if len(tokens) == 1:
                    print('read_cell: Warning - ignoring unit specifier in')
                    print('%BLOCK POSITIONS_ABS(assuming Angstrom instead)')
                    tokens, l = get_tokens(lines, l)
                while len(tokens) == 4:
                    spec.append(tokens[0])
                    pos.append([float(p) for p in tokens[1:4]])
                    tokens, l = get_tokens(lines, l)
                if tokens[0].upper() != "%ENDBLOCK":
                    print('read_cell: Warning - ignoring invalid lines in')
                    print('%%BLOCK POSITIONS_ABS:\n\t %s' % tokens)
                have_pos = True

            elif tokens[1].upper() == "POSITIONS_FRAC" and not have_pos:
                pos_frac = True
                tokens, l = get_tokens(lines, l)
                while len(tokens) == 4:
                    spec.append(tokens[0])
                    pos.append([float(p) for p in tokens[1:4]])
                    tokens, l = get_tokens(lines, l)
                if tokens[0].upper() != "%ENDBLOCK":
                    print('read_cell: Warning - ignoring invalid lines')
                    print('%%BLOCK POSITIONS_FRAC:\n\t %s' % tokens)
                have_pos = True
            elif tokens[1].upper() == 'SPECIES_POT':
                tokens, l = get_tokens(lines, l)
                while tokens and not tokens[0].upper() == '%ENDBLOCK':
                    if len(tokens) == 2:
                        calc.cell.species_pot = tuple(tokens)
                    tokens, l = get_tokens(lines, l)
            elif tokens[1].upper() == 'IONIC_CONSTRAINTS':

                while True:
                    if tokens and tokens[0].upper() == '%ENDBLOCK':
                        break
                    tokens, l = get_tokens(lines, l)
                    if not len(tokens) == 6:
                        continue
                    _, species, nic, x, y, z = tokens
                    nic = int(nic)
                    if (species, nic) not in raw_constraints:
                        raw_constraints[(species, nic)] = []
                    raw_constraints[(species, nic)].append(array(
                                                           [x, y, z]))
            else:
                print('Warning: the keyword %s is not' % tokens[1].upper())
                print('         interpreted in cell files')
                while not tokens[0].upper() == '%ENDBLOCK':
                    tokens, l = get_tokens(lines, l)
                #raise UserWarning
        else:
            key = tokens[0]
            value = ' '.join(tokens[1:])
            try:
                calc.__setattr__(key, value)
            except:
                print("Problem setting calc.cell.%s = %s" % (key, value))
                raise

    if pos_frac:
        atoms = ase.Atoms(
            calculator=calc,
            cell=lat,
            pbc=True,
            scaled_positions=pos,
            symbols=spec,
            )
    else:
        atoms = ase.Atoms(
            calculator=calc,
            cell=lat,
            pbc=True,
            positions=pos,
            symbols=spec,
            )

    fixed_atoms = []
    for (species, nic), value in raw_constraints.items():
        absolute_nr = atoms.calc._get_absolute_number(species, nic)
        if len(value) == 3:
            fixed_atoms.append(absolute_nr)
        elif len(value) == 2:
            constraint = ase.constraints.FixedLine(a=absolute_nr,
                direction=cross(value[0], value[1]))
            constraints.append(constraint)
        elif len(value) == 1:
            constraint = ase.constraints.FixedPlane(a=absolute_nr,
                direction=array(value[0], dtype=float32))
            constraints.append(constraint)
        else:
            print('Error: Found %s statements attached to atoms %s'
                  % (len(value), absolute_nr))
    constraints.append(ase.constraints.FixAtoms(fixed_atoms))
    atoms.set_constraint(constraints)

    # needs to go here again to have the constraints in
    # atoms.calc.atoms.constraints as well
    atoms.calc.atoms = atoms
    atoms.calc.push_oldstate()
    return atoms
Ejemplo n.º 3
0
def read_castep_cell(fd, index=None):
    """Read a .cell file and return an atoms object.
    Any value found that does not fit the atoms API
    will be stored in the atoms.calc attribute.

    This routine has been modified to also be able to read *.cell files even if
    there is no CASTEP installation or castep_keywords.py available. We wil
    then make use of a fallback-mode which basically just read atoms positions
    and unit cell information. This can very highly useful for visualization
    using the ASE gui.
    """

    from ase.calculators.castep import Castep

    _fallback = False
    try:
        calc = Castep()
    except Exception as exception:
        print('read_cell: Warning - Was not able to initialize CASTEP '
              'calculator.')
        print('           This may be due to a non-existing '
              '"castep.keywords.py"')
        print('           file or a non-existing CASTEP installation.')
        print('           Original error message appears below:')
        print('')
        print(' ' * 11 + exception.__str__().replace('\n', '\n' + ' ' * 11))
        print('')
        print(
            '           Fallback-mode will be applied to provide at least the')
        print('           geometric information contained in the *.cell file.')
        calc = None
        _fallback = True

    # fd will be closed by embracing read() routine
    lines = fd.readlines()

    def get_tokens(lines, l, maxsplit=0):
        """Tokenizes one line of a *cell file."""
        comment_chars = '#!;'
        separator_re = '[\s=:]+'
        while l < len(lines):
            line = lines[l].strip()
            if len(line) == 0:
                l += 1
                continue
            elif any([line.startswith(comment_char)
                      for comment_char in comment_chars]):
                l += 1
                continue
            else:
                # Remove comments
                line = re.split('[{0}]+'.format(comment_chars), line, 1)[0]
                # Tokenize
                tokens = re.split(separator_re, line.strip(), maxsplit)
                return tokens, l + 1
        tokens = ''

    # This print statement is definitely not necessary
    #    print("read_cell: Warning - get_tokens has not found any more tokens")
        return tokens, l

    lat = []
    have_lat = False

    pos = []
    spec = []

    # Here we extract all the possible additional info
    # These are marked by their type
    add_info = {
        'SPIN': float,
        'MAGMOM': float,
        'LABEL': str,
    }
    add_info_arrays = dict((k, []) for k in add_info)

    # A convenient function that extracts this info from a line fragment
    def get_add_info(ai_arrays, line=''):
        re_keys = '({0})'.format('|'.join(add_info.keys()))
        ai_dict = {}
        sline = re.split(re_keys, line, flags=re.IGNORECASE)
        for t_i, tok in enumerate(sline):
            if tok in add_info:
                try:
                    ai_dict[tok] = re.split('[:=]',
                                            sline[t_i + 1],
                                            maxsplit=1)[1].strip()
                except IndexError:
                    ai_dict[tok] = None
        # Then turn these into values into the arrays
        for k in ai_arrays:
            if k not in ai_dict or ai_dict[k] is None:
                ai_arrays[k].append({str: 'NULL',
                                     float: 0.0,
                                     }[add_info[k]])
            else:
                ai_arrays[k].append(add_info[k](ai_dict[k]))

    constraints = []
    raw_constraints = {}
    have_pos = False
    pos_frac = False

    l = 0
    while l < len(lines):
        tokens, l = get_tokens(lines, l)
        if not tokens:
            continue
        elif tokens[0].upper() == '%BLOCK':
            if tokens[1].upper() == 'LATTICE_CART' and not have_lat:
                tokens, l = get_tokens(lines, l)
                if len(tokens) == 1:
                    print('read_cell: Warning - ignoring unit specifier in')
                    print('%BLOCK LATTICE_CART (assuming Angstrom instead)')
                    tokens, l = get_tokens(lines, l)
                for _ in range(3):
                    lat_vec = [float(a) for a in tokens[0:3]]
                    lat.append(lat_vec)
                    tokens, l = get_tokens(lines, l)
                if tokens[0].upper() != '%ENDBLOCK':
                    print('read_cell: Warning - ignoring more than three')
                    print('lattice vectors in invalid %BLOCK LATTICE_CART')
                    print('%s ...' % tokens[0].upper())
                have_lat = True

            elif tokens[1].upper() == 'LATTICE_ABC' and not have_lat:
                tokens, l = get_tokens(lines, l)
                if len(tokens) == 1:
                    print('read_cell: Warning - ignoring unit specifier in')
                    print('%BLOCK LATTICE_ABC (assuming Angstrom instead)')
                    tokens, l = get_tokens(lines, l)
                a, b, c = map(float, tokens[0:3])
                tokens, l = get_tokens(lines, l)
                alpha, beta, gamma = [np.radians(float(phi))
                                      for phi in tokens[0:3]]
                tokens, l = get_tokens(lines, l)
                if tokens[0].upper() != '%ENDBLOCK':
                    print('read_cell: Warning - ignoring additional lines in')
                    print('invalid %BLOCK LATTICE_ABC')
                lat_a = [a, 0, 0]
                lat_b = [b * np.cos(gamma), b * np.sin(gamma), 0]
                lat_c1 = c * np.cos(beta)
                lat_c2 = c * ((np.cos(alpha) - np.cos(beta) * np.cos(gamma)) /
                              np.sin(gamma))
                lat_c3 = np.sqrt(c * c - lat_c1 * lat_c1 - lat_c2 * lat_c2)
                lat_c = [lat_c1, lat_c2, lat_c3]
                lat = [lat_a, lat_b, lat_c]
                have_lat = True

            elif tokens[1].upper() == 'POSITIONS_ABS' and not have_pos:
                tokens, l = get_tokens(lines, l)
                if len(tokens) == 1:
                    print('read_cell: Warning - ignoring unit specifier in')
                    print('%BLOCK POSITIONS_ABS(assuming Angstrom instead)')
                    tokens, l = get_tokens(lines, l, maxsplit=4)
                # fix to be able to read initial spin assigned on the atoms
                while len(tokens) >= 4:
                    spec.append(tokens[0])
                    pos.append([float(p) for p in tokens[1:4]])
                    if len(tokens) > 4:
                        get_add_info(add_info_arrays, tokens[4])
                    else:
                        get_add_info(add_info_arrays)
                    tokens, l = get_tokens(lines, l, maxsplit=4)
                if tokens[0].upper() != '%ENDBLOCK':
                    print('read_cell: Warning - ignoring invalid lines in')
                    print('%%BLOCK POSITIONS_ABS:\n\t %s' % tokens)
                have_pos = True

            elif tokens[1].upper() == 'POSITIONS_FRAC' and not have_pos:
                pos_frac = True
                tokens, l = get_tokens(lines, l, maxsplit=4)
                # fix to be able to read initial spin assigned on the atoms
                while len(tokens) >= 4:
                    spec.append(tokens[0])
                    pos.append([float(p) for p in tokens[1:4]])
                    if len(tokens) > 4:
                        get_add_info(add_info_arrays, tokens[4])
                    else:
                        get_add_info(add_info_arrays)
                    tokens, l = get_tokens(lines, l, maxsplit=4)
                if tokens[0].upper() != '%ENDBLOCK':
                    print('read_cell: Warning - ignoring invalid lines')
                    print('%%BLOCK POSITIONS_FRAC:\n\t %s' % tokens)
                have_pos = True
            elif tokens[1].upper() == 'SPECIES_POT':
                if not _fallback:
                    tokens, l = get_tokens(lines, l)
                    while tokens and not tokens[0].upper() == '%ENDBLOCK':
                        if len(tokens) == 2:
                            calc.cell.species_pot = tuple(tokens)
                        tokens, l = get_tokens(lines, l)
            elif tokens[1].upper() == 'IONIC_CONSTRAINTS':

                while True:
                    if tokens and tokens[0].upper() == '%ENDBLOCK':
                        break
                    tokens, l = get_tokens(lines, l)
                    if not len(tokens) == 6:
                        continue
                    _, species, nic, x, y, z = tokens
                    # convert xyz to floats
                    x = float(x)
                    y = float(y)
                    z = float(z)

                    nic = int(nic)
                    if (species, nic) not in raw_constraints:
                        raw_constraints[(species, nic)] = []
                    raw_constraints[(species, nic)].append(np.array(
                                                           [x, y, z]))

            else:
                print('Warning: the keyword %s is not' % tokens[1].upper())
                print('         interpreted in cell files')
                while not tokens[0].upper() == '%ENDBLOCK':
                    tokens, l = get_tokens(lines, l)
                # raise UserWarning
        else:
            key = tokens[0]
            value = ' '.join(tokens[1:])
            if not _fallback:
                try:
                    calc.__setattr__(key, value)
                except:
                    print('Problem setting calc.cell.%s = %s' % (key, value))
                    raise

    # Get the relevant additional info
    magmom = np.array(add_info_arrays['SPIN'])
    # SPIN or MAGMOM are alternative keywords
    magmom = np.where(magmom != 0, magmom, add_info_arrays['MAGMOM'])
    labels = np.array(add_info_arrays['LABEL'])

    if pos_frac:
        atoms = ase.Atoms(
            calculator=calc,
            cell=lat,
            pbc=True,
            scaled_positions=pos,
            symbols=spec,
            magmoms=magmom)
    else:
        atoms = ase.Atoms(
            calculator=calc,
            cell=lat,
            pbc=True,
            positions=pos,
            symbols=spec,
            magmoms=magmom)

    atoms.new_array('castep_labels', labels)

    fixed_atoms = []
    for (species, nic), value in raw_constraints.items():
        absolute_nr = atoms.calc._get_absolute_number(species, nic)
        if len(value) == 3:
            fixed_atoms.append(absolute_nr)
        elif len(value) == 2:
            constraint = ase.constraints.FixedLine(
                a=absolute_nr,
                direction=np.cross(value[0], value[1]))
            constraints.append(constraint)
        elif len(value) == 1:
            # catch cases in which constraints are given in a single line in
            # the cell file
            # if np.count_nonzero(value[0]) == 3:
            #     fixed_atoms.append(absolute_nr)
            # elif np.count_nonzero(value[0]) == 2:
            #     # in this case we need a FixedLine instance
            #     # it is initialized with the atom's index
            #     constraint = ase.constraints.FixedLine(a=absolute_nr,
            #         direction=[not v for v in value[0]])
            #     constraints.append(constraint)
            # else:

            # I do not think you can have a fixed position of a fixed
            # line with only one constraint -- JML
            constraint = ase.constraints.FixedPlane(
                a=absolute_nr,
                direction=np.array(value[0], dtype=np.float32))
            constraints.append(constraint)
        else:
            print('Error: Found %s statements attached to atoms %s'
                  % (len(value), absolute_nr))
    # we need to sort the fixed atoms list in order not to raise an assertion
    # error in FixAtoms
    if fixed_atoms:
        constraints.append(
            ase.constraints.FixAtoms(indices=sorted(fixed_atoms)))
    if constraints:
        atoms.set_constraint(constraints)

    if not _fallback:
        # needs to go here again to have the constraints in
        # atoms.calc.atoms.constraints as well
        atoms.calc.atoms = atoms
        atoms.calc.push_oldstate()
    return atoms