UNPKG

@ccp-nc/crystvis-js

Version:

A Three.js based crystallographic visualisation tool

371 lines (314 loc) 10.4 kB
'use strict'; /** * @fileoverview Function for loading Magres files * @module */ import _ from 'lodash'; import { Atoms } from '@ccp-nc/crystcif-parse'; import { TensorData } from '../tensor.js'; const MagresUnits = { length: { ang: 1.0, Angstrom: 1.0 }, sus: { '10^-6.cm^3.mol^-1': 1.0 }, ms: { ppm: 1.0 }, efg: { au: 1.0 }, isc: { '10^19.T^2.J^-1': 1.0 } }; // Magres parsing utility functions function parseNoAtomLine(line, units) { // Assumed to be just nine numbers, a full 3x3 matrix. // Used for susceptibility let tens = []; for (let i = 0; i < 3; ++i) { tens.push([]); for (let j = 0; j < 3; ++j) { tens[i].push(parseFloat(line[3 * i + j]) * units); } } return new TensorData(tens); } function parseOneAtomLine(line, units, labels) { // Species label + index, then nine numbers let sp = line[0]; // CASTEP <22.1.1 has a bug where species with in in-species index >99 // runs into the species label. // To handle those cases, we need to check if the species label has // an integer >99 at the end, and if so, remove it from the label // and add it to the index. // use regex to find any integer at the end of the species label let sp_re = /([a-zA-Z]+)([0-9]+)$/; let sp_match = sp.match(sp_re); if (sp_match) { // if the integer is >99 and the line has 10 elements instead of 11, // remove it from the label and add it to the index let rogue_int = parseInt(sp_match[2]); if (rogue_int > 99 && line.length == 10) { console.log(` Badly formatted .magres file. A line has a species label with an integer >99 at the end, which suggests a writing-overflow (known bug in CASTEP). e.g. 'Fe100' instead of 'Fe 100'. The integer has been removed from the label and added to the index. `) sp = sp_match[1]; // insert the rogue integer into // the line array at the correct position line.splice(1, 0, rogue_int); } } let sp_i = parseInt(line[1]); let i = _.findIndex(labels, function(x) { return (x[0] == sp) && (x[1] == sp_i); }); let ltrim = Array.from(line).splice(2); let ans = { i: i, tens: parseNoAtomLine(ltrim, units) } return ans; } function parseTwoAtomLine(line, units, labels) { let sp1 = line[0] let sp1_i = parseInt(line[1]); let i1 = _.findIndex(labels, function(x) { return (x[0] == sp1) && (x[1] == sp1_i); }); let ltrim = Array.from(line).splice(2); let ans = parseOneAtomLine(ltrim, units, labels); ans = { i1: i1, i2: ans.i, tens: ans.tens }; return ans; } const MagresParsers = { sus: parseNoAtomLine, ms: parseOneAtomLine, efg: parseOneAtomLine, isc: parseTwoAtomLine }; function load(contents, filename='magres') { const known_blocks = ['atoms', 'magres']; let lines = _.split(contents, '\n'); // First line contains version let v_re = /#\$magres-abinitio-v([0-9]+\.[0-9]+)/; let v_match = lines[0].match(v_re); if (!v_match) { throw Error('Invalid Magres file format: no version line'); } let version = v_match[1]; // Start by identifying blocks let blocks = {}; let b_re = /\[(\/)?([a-zA-Z_]+)\]/; let b_alt_re = /<(\/)?([a-zA-Z_]+)>/; // Alternative version (old magres tags) let mtagtype_detected = false; // Have we found out which type of tags are used? let block_name = null; let block_lines = []; for (let i = 1; i < lines.length; ++i) { let b_match = lines[i].match(b_re); if (!mtagtype_detected) { let b_alt_match = lines[i].match(b_alt_re); if (b_alt_match) { mtagtype_detected = true; b_re = b_alt_re; b_match = b_alt_match; } else if (b_match) { mtagtype_detected = true; } } if (b_match) { // Start or end? if (b_match[1] == '/') { if (b_match[2] == block_name) { // End block blocks[block_name] = block_lines; block_name = null; block_lines = []; } else { throw Error('Invalid Magres file format: block closed without opening'); } } else { if (block_name) { throw Error('Invalid Magres file format: block opened without closing'); } block_name = b_match[2]; } } else if (block_name) { block_lines.push(lines[i]); } } // Process each block for (let bname in blocks) { let block = blocks[bname]; if (!known_blocks.includes(bname)) { // We don't know this block blocks[bname] = _.join(block, '\n'); continue; } let data = {}; for (let i = 0; i < block.length; ++i) { let l = block[i]; let lspl = _.trim(l).split(/\s+/); // Is it a 'units' line? if (lspl[0] === 'units') { let tag = lspl[1]; if (tag in data && data[tag].lines.length > 0) { // We're being forgiving only of the case where a unit definition is duplicated throw Error('Invalid Magres file format: units specified after tag ' + tag + ' has been used'); } data[tag] = { 'units': lspl[2], 'lines': [] }; } else if (lspl[0] !== '') { let tag = lspl[0]; if (!(tag in data)) { data[tag] = { 'units': null, 'lines': [] }; } data[tag].lines.push(lspl.splice(1)); } } blocks[bname] = data; } // Now on to read the blocks themselves let ablock = blocks.atoms; if (!ablock) { throw Error('Invalid Magres file format: does not contain atoms block'); } // Read in the cell, if present let cell = null; if ('lattice' in ablock) { let u = 1.0; let uname = ablock.lattice.units; if (uname) { u = MagresUnits.length[uname]; if (!u) { throw Error('Invalid Magres file format: invalid units for cell'); } } cell = []; let line = Array.from(ablock.lattice.lines[0]); for (let i = 0; i < 3; ++i) { cell.push(_.map(line.splice(0, 3), function(x) { return parseFloat(x) * u; })); } } // Reject if we don't have a cell if (!cell) { throw Error('No unit cell found in Magres file. We only support Magres files with a Lattice block.'); } // Read in the atom positions and species let elems = []; let pos = []; let mlabels = []; let labels = []; if ('atom' in ablock) { let u = 1.0; let uname = ablock.atom.units; if (uname) { u = MagresUnits.length[uname]; if (!u) { throw Error('Invalid Magres file format: invalid units for atom'); } } for (let i = 0; i < ablock.atom.lines.length; ++i) { let l = ablock.atom.lines[i]; elems.push(l[0]); mlabels.push([l[1], parseInt(l[2])]); labels.push(l[1]); pos.push(_.map(l.slice(3,6), function(x) { return parseFloat(x) * u; })); } } else { throw Error('Invalid Magres file format: no atom position data found'); } // Create atoms object let atoms = new Atoms(elems, pos, cell, { 'magres-blocks': blocks, 'magres-version': version, }); // Add array atoms.set_array('labels', labels); atoms.set_array('magres-labels', mlabels); let N = elems.length; // Now for the magres stuff let mblock = blocks.magres; if (mblock) { for (let tag in mblock) { let tag_type = tag.split('_')[0]; let u = 1.0; let uname = mblock[tag].units; if (uname) { u = MagresUnits[tag_type][uname]; if (!u) { throw Error('Invalid Magres file format: invalid units for ' + tag); } } // Initialise as dictionary let tag_dimension; let tag_data; switch (MagresParsers[tag_type]) { case parseNoAtomLine: tag_dimension = 0; break; case parseOneAtomLine: tag_dimension = 1; tag_data = new Array(N); break; case parseTwoAtomLine: tag_dimension = 2; tag_data = _.times(N, function() { return new Array(N); }); } for (let i = 0; i < mblock[tag].lines.length; ++i) { let l = mblock[tag].lines[i]; let data = MagresParsers[tag_type](l, u, mlabels); switch (tag_dimension) { case 0: tag_data = data; break; case 1: tag_data[data.i] = data.tens; break; case 2: tag_data[data.i1][data.i2] = data.tens; tag_data[data.i2][data.i1] = data.tens; break; } } if (tag_dimension == 0) { atoms.info[tag] = tag_data; } else { atoms.set_array(tag, tag_data); } } } let structs = {}; structs[filename] = atoms; return structs; } export { load };