Source code for ase.io.magres

# fmt: off

"""This module provides I/O functions for the MAGRES file format, introduced
by CASTEP as an output format to store structural data and ab-initio
calculated NMR parameters.
Authors: Simone Sturniolo (ase implementation), Tim Green (original magres
    parser code)
"""

from collections import OrderedDict

import numpy as np

import ase.io._magres as _magres
import ase.units
from ase.atoms import Atoms
from ase.calculators.singlepoint import SinglePointDFTCalculator
from ase.spacegroup import Spacegroup
from ase.spacegroup.spacegroup import SpacegroupNotFoundError

_mprops = {
    'ms': ('sigma', 1),
    'sus': ('S', 0),
    'efg': ('V', 1),
    'isc': ('K', 2)}
# (matrix name, number of atoms in interaction) for various magres quantities


[docs] def read_magres(fd, include_unrecognised=False): """Read magres file.""" block_parsers = {'magres': _magres.parse_magres_block, 'atoms': _magres.parse_atoms_block, 'calculation': _magres.parse_generic_block, } file_contents = fd.read() # This works as a validity check version = _magres.get_version(file_contents) if version is None: # This isn't even a .magres file! raise RuntimeError('File is not in standard Magres format') blocks = _magres.parse_blocks(file_contents) data_dict = {} for block_data in blocks: block = _magres.parse_block(block_data) if block[0] in block_parsers: block_dict = block_parsers[block[0]](block) data_dict[block[0]] = block_dict else: # Throw in the text content of blocks we don't recognise if include_unrecognised: data_dict[block[0]] = block_data[1] # Now the loaded data must be turned into an ASE Atoms object # First check if the file is even viable if 'atoms' not in data_dict: raise RuntimeError('Magres file does not contain structure data') # Allowed units handling. This is redundant for now but # could turn out useful in the future magres_units = {'Angstrom': ase.units.Ang} # Lattice parameters? if 'lattice' in data_dict['atoms']: try: u = dict(data_dict['atoms']['units'])['lattice'] except KeyError: raise RuntimeError('No units detected in file for lattice') u = magres_units[u] cell = np.array(data_dict['atoms']['lattice'][0]) * u pbc = True else: cell = None pbc = False # Now the atoms symbols = [] positions = [] indices = [] labels = [] if 'atom' in data_dict['atoms']: try: u = dict(data_dict['atoms']['units'])['atom'] except KeyError: raise RuntimeError('No units detected in file for atom positions') u = magres_units[u] # Now we have to account for the possibility of there being CASTEP # 'custom' species amongst the symbols custom_species = None for a in data_dict['atoms']['atom']: spec_custom = a['species'].split(':', 1) if len(spec_custom) > 1 and custom_species is None: # Add it to the custom info! custom_species = list(symbols) symbols.append(spec_custom[0]) positions.append(a['position']) indices.append(a['index']) labels.append(a['label']) if custom_species is not None: custom_species.append(a['species']) atoms = Atoms(cell=cell, pbc=pbc, symbols=symbols, positions=positions) # Add custom species if present if custom_species is not None: atoms.new_array('castep_custom_species', np.array(custom_species)) # Add the spacegroup, if present and recognizable if 'symmetry' in data_dict['atoms']: try: spg = Spacegroup(data_dict['atoms']['symmetry'][0]) except SpacegroupNotFoundError: # Not found spg = Spacegroup(1) # Most generic one atoms.info['spacegroup'] = spg # Set up the rest of the properties as arrays atoms.new_array('indices', np.array(indices)) atoms.new_array('labels', np.array(labels)) # Now for the magres specific stuff li_list = list(zip(labels, indices)) def create_magres_array(name, order, block): if order == 1: u_arr = [None] * len(li_list) elif order == 2: u_arr = [[None] * (i + 1) for i in range(len(li_list))] else: raise ValueError( 'Invalid order value passed to create_magres_array') for s in block: # Find the atom index/indices if order == 1: # First find out which atom this is at = (s['atom']['label'], s['atom']['index']) try: ai = li_list.index(at) except ValueError: raise RuntimeError('Invalid data in magres block') # Then add the relevant quantity u_arr[ai] = s[mn] else: at1 = (s['atom1']['label'], s['atom1']['index']) at2 = (s['atom2']['label'], s['atom2']['index']) ai1 = li_list.index(at1) ai2 = li_list.index(at2) # Sort them ai1, ai2 = sorted((ai1, ai2), reverse=True) u_arr[ai1][ai2] = s[mn] if order == 1: return np.array(u_arr) else: return np.array(u_arr, dtype=object) if 'magres' in data_dict: if 'units' in data_dict['magres']: atoms.info['magres_units'] = dict(data_dict['magres']['units']) for u in atoms.info['magres_units']: # This bit to keep track of tags u0 = u.split('_')[0] if u0 not in _mprops: raise RuntimeError('Invalid data in magres block') mn, order = _mprops[u0] if order > 0: u_arr = create_magres_array(mn, order, data_dict['magres'][u]) atoms.new_array(u, u_arr) else: # atoms.info['magres_data'] = atoms.info.get('magres_data', # {}) # # We only take element 0 because for this sort of data # # there should be only that # atoms.info['magres_data'][u] = \ # data_dict['magres'][u][0][mn] if atoms.calc is None: calc = SinglePointDFTCalculator(atoms) atoms.calc = calc atoms.calc.results[u] = data_dict['magres'][u][0][mn] if 'calculation' in data_dict: atoms.info['magresblock_calculation'] = data_dict['calculation'] if include_unrecognised: for b in data_dict: if b not in block_parsers: atoms.info['magresblock_' + b] = data_dict[b] return atoms
def tensor_string(tensor): return ' '.join(' '.join(str(x) for x in xs) for xs in tensor)
[docs] def write_magres(fd, image): """ A writing function for magres files. Two steps: first data are arranged into structures, then dumped to the actual file """ image_data = {} image_data['atoms'] = {'units': []} # Contains units, lattice and each individual atom if np.all(image.get_pbc()): # Has lattice! image_data['atoms']['units'].append(['lattice', 'Angstrom']) image_data['atoms']['lattice'] = [image.get_cell()] # Now for the atoms if image.has('labels'): labels = image.get_array('labels') else: labels = image.get_chemical_symbols() if image.has('indices'): indices = image.get_array('indices') else: indices = [labels[:i + 1].count(labels[i]) for i in range(len(labels))] # Iterate over atoms symbols = (image.get_array('castep_custom_species') if image.has('castep_custom_species') else image.get_chemical_symbols()) atom_info = list(zip(symbols, image.get_positions())) if len(atom_info) > 0: image_data['atoms']['units'].append(['atom', 'Angstrom']) image_data['atoms']['atom'] = [] for i, a in enumerate(atom_info): image_data['atoms']['atom'].append({ 'index': indices[i], 'position': a[1], 'species': a[0], 'label': labels[i]}) # Spacegroup, if present if 'spacegroup' in image.info: image_data['atoms']['symmetry'] = [image.info['spacegroup'] .symbol.replace(' ', '')] # Now go on to do the same for magres information if 'magres_units' in image.info: image_data['magres'] = {'units': []} for u in image.info['magres_units']: # Get the type p = u.split('_')[0] if p in _mprops: image_data['magres']['units'].append( [u, image.info['magres_units'][u]]) image_data['magres'][u] = [] mn, order = _mprops[p] if order == 0: # The case of susceptibility tens = { mn: image.calc.results[u] } image_data['magres'][u] = tens else: arr = image.get_array(u) li_tab = zip(labels, indices) for i, (lab, ind) in enumerate(li_tab): if order == 2: for j, (lab2, ind2) in enumerate(li_tab[:i + 1]): if arr[i][j] is not None: tens = {mn: arr[i][j], 'atom1': {'label': lab, 'index': ind}, 'atom2': {'label': lab2, 'index': ind2}} image_data['magres'][u].append(tens) elif order == 1: if arr[i] is not None: tens = {mn: arr[i], 'atom': {'label': lab, 'index': ind}} image_data['magres'][u].append(tens) # Calculation block, if present if 'magresblock_calculation' in image.info: image_data['calculation'] = image.info['magresblock_calculation'] def write_units(data, out): if 'units' in data: for tag, units in data['units']: out.append(f' units {tag} {units}') def write_magres_block(data): """ Write out a <magres> block from its dictionary representation """ out = [] def nout(tag, tensor_name): if tag in data: out.append(' '.join([' ', tag, tensor_string(data[tag][tensor_name])])) def siout(tag, tensor_name): if tag in data: for atom_si in data[tag]: out.append((' %s %s %d ' '%s') % (tag, atom_si['atom']['label'], atom_si['atom']['index'], tensor_string(atom_si[tensor_name]))) write_units(data, out) nout('sus', 'S') siout('ms', 'sigma') siout('efg_local', 'V') siout('efg_nonlocal', 'V') siout('efg', 'V') def sisiout(tag, tensor_name): if tag in data: for isc in data[tag]: out.append((' %s %s %d %s %d ' '%s') % (tag, isc['atom1']['label'], isc['atom1']['index'], isc['atom2']['label'], isc['atom2']['index'], tensor_string(isc[tensor_name]))) sisiout('isc_fc', 'K') sisiout('isc_orbital_p', 'K') sisiout('isc_orbital_d', 'K') sisiout('isc_spin', 'K') sisiout('isc', 'K') return '\n'.join(out) def write_atoms_block(data): out = [] write_units(data, out) if 'lattice' in data: for lat in data['lattice']: out.append(f" lattice {tensor_string(lat)}") if 'symmetry' in data: for sym in data['symmetry']: out.append(f' symmetry {sym}') if 'atom' in data: for a in data['atom']: out.append((' atom %s %s %s ' '%s') % (a['species'], a['label'], a['index'], ' '.join(str(x) for x in a['position']))) return '\n'.join(out) def write_generic_block(data): out = [] for tag, data in data.items(): for value in data: out.append('{} {}'.format(tag, ' '.join(str(x) for x in value))) return '\n'.join(out) # Using this to preserve order block_writers = OrderedDict([('calculation', write_generic_block), ('atoms', write_atoms_block), ('magres', write_magres_block)]) # First, write the header fd.write('#$magres-abinitio-v1.0\n') fd.write('# Generated by the Atomic Simulation Environment library\n') for b in block_writers: if b in image_data: fd.write(f'[{b}]\n') fd.write(block_writers[b](image_data[b])) fd.write(f'\n[/{b}]\n') # Now on to check for any non-standard blocks... for i in image.info: if '_' in i: ismag, b = i.split('_', 1) if ismag == 'magresblock' and b not in block_writers: fd.write(f'[{b}]\n') fd.write(image.info[i]) fd.write(f'[/{b}]\n')