molstar
Version:
A comprehensive macromolecular library.
173 lines (172 loc) • 7.34 kB
JavaScript
/**
* Copyright (c) 2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
* @author Ludovic Autin <ludovic.autin@gmail.com>
*/
import { Task, chunkedSubtask } from '../../../../mol-task';
import { Tokenizer, TokenBuilder } from '../../common/text/tokenizer';
import { ReaderResult as Result } from '../../result';
import { TokenColumnProvider as TokenColumn } from '../../common/text/column/token';
import { Column } from '../../../../mol-data/db';
const { readLine, skipWhitespace, eatValue, eatLine, markStart } = Tokenizer;
function State(tokenizer, runtimeCtx) {
return {
tokenizer,
runtimeCtx,
};
}
async function handleAtoms(state, count, parts) {
const { tokenizer } = state;
const columnIndexMap = Object.fromEntries(parts.map((colName, index) => [colName, index]));
// declare column x, y, and z by check first caracter to 'x' or 'y' or 'z'
// x,y,z = unscaled atom coordinates
// xs,ys,zs = scaled atom coordinates this need the boundary box
// xu,yu,zu = unwrapped atom coordinates
// xsu,ysu,zsu = scaled unwrapped atom coordinates
// ix,iy,iz = box image that the atom is in
// how should we handle the different scenario ?
const xCol = parts.findIndex(p => p[0] === 'x');
const yCol = parts.findIndex(p => p[0] === 'y');
const zCol = parts.findIndex(p => p[0] === 'z');
// retrieve the atom type colum for x only
const atomMode = parts[xCol]; // x,xs,xu,xsu
const atomId = TokenBuilder.create(tokenizer.data, count * 2);
const moleculeType = TokenBuilder.create(tokenizer.data, count * 2);
const atomType = TokenBuilder.create(tokenizer.data, count * 2);
const x = TokenBuilder.create(tokenizer.data, count * 2);
const y = TokenBuilder.create(tokenizer.data, count * 2);
const z = TokenBuilder.create(tokenizer.data, count * 2);
const { position } = tokenizer;
tokenizer.position = position;
const n = parts.length;
const { length } = tokenizer;
let linesAlreadyRead = 0;
await chunkedSubtask(state.runtimeCtx, 100000, void 0, chunkSize => {
const linesToRead = Math.min(count - linesAlreadyRead, chunkSize);
for (let i = 0; i < linesToRead; ++i) {
for (let j = 0; j < n; ++j) {
skipWhitespace(tokenizer);
markStart(tokenizer);
eatValue(tokenizer);
switch (j) {
case columnIndexMap['id']:
TokenBuilder.addUnchecked(atomId, tokenizer.tokenStart, tokenizer.tokenEnd);
break;
case columnIndexMap['mol']:
TokenBuilder.addUnchecked(moleculeType, tokenizer.tokenStart, tokenizer.tokenEnd);
break;
case columnIndexMap['type']:
TokenBuilder.addUnchecked(atomType, tokenizer.tokenStart, tokenizer.tokenEnd);
break;
case xCol:
TokenBuilder.addUnchecked(x, tokenizer.tokenStart, tokenizer.tokenEnd);
break;
case yCol:
TokenBuilder.addUnchecked(y, tokenizer.tokenStart, tokenizer.tokenEnd);
break;
case zCol:
TokenBuilder.addUnchecked(z, tokenizer.tokenStart, tokenizer.tokenEnd);
break;
}
}
// ignore any extra columns
eatLine(tokenizer);
markStart(tokenizer);
}
linesAlreadyRead += linesToRead;
return linesToRead;
}, ctx => ctx.update({ message: 'Parsing...', current: tokenizer.position, max: length }));
return {
count,
atomMode: atomMode,
atomId: TokenColumn(atomId)(Column.Schema.int),
moleculeId: TokenColumn(moleculeType)(Column.Schema.int),
atomType: TokenColumn(atomType)(Column.Schema.int),
x: TokenColumn(x)(Column.Schema.float),
y: TokenColumn(y)(Column.Schema.float),
z: TokenColumn(z)(Column.Schema.float),
};
}
/**
* Possible Attributes from Lammps Dump
* see https://docs.lammps.org/dump.html fro more details
* possible attributes = id, mol, proc, procp1, type, element, mass,
* x, y, z, xs, ys, zs, xu, yu, zu,
* xsu, ysu, zsu, ix, iy, iz,
* vx, vy, vz, fx, fy, fz,
* q, mux, muy, muz, mu,
* radius, diameter, omegax, omegay, omegaz,
* angmomx, angmomy, angmomz, tqx, tqy, tqz,
* c_ID, c_ID[I], f_ID, f_ID[I], v_name,
* i_name, d_name, i2_name[I], d2_name[I]
* ITEM: BOX BOUNDS xx yy zz
* xlo xhi
* ylo yhi
* zlo zhi
*/
async function parseInternal(data, ctx) {
const tokenizer = Tokenizer(data);
const state = State(tokenizer, ctx);
const f = {
frames: [],
times: [],
bounds: [],
timeOffset: 0.0,
deltaTime: 0.0
};
const frames = f.frames;
let numAtoms = 0;
let timestep = 0;
while (tokenizer.tokenEnd < tokenizer.length) {
const line = readLine(state.tokenizer).trim();
if (line.includes('ITEM: TIMESTEP')) {
timestep = parseInt(readLine(state.tokenizer).trim());
f.times.push(timestep);
}
else if (line.includes('ITEM: NUMBER OF ATOMS')) {
numAtoms = parseInt(readLine(state.tokenizer).trim());
}
else if (line.includes('ITEM: ATOMS')) {
// this line provide also the style of the output and will give the order of the columns
const parts = line.split(' ').slice(2);
const frame = await handleAtoms(state, numAtoms, parts);
frames.push(frame);
}
else if (line.includes('ITEM: BOX BOUNDS')) {
const tokens = line.split('ITEM: BOX BOUNDS ')[1].split(' ');
// Periodicity of the box
const px = tokens[0];
const py = tokens[1];
const pz = tokens[2];
// the actual box bounds
const xbound = readLine(state.tokenizer).trim().split(' ');
const ybound = readLine(state.tokenizer).trim().split(' ');
const zbound = readLine(state.tokenizer).trim().split(' ');
const xlo = parseFloat(xbound[0]);
const xhi = parseFloat(xbound[1]);
const ylo = parseFloat(ybound[0]);
const yhi = parseFloat(ybound[1]);
const zlo = parseFloat(zbound[0]);
const zhi = parseFloat(zbound[1]);
f.bounds.push({
lower: [xlo, ylo, zlo],
length: [xhi - xlo, yhi - ylo, zhi - zlo],
periodicity: [px, py, pz]
});
}
}
if (f.times.length >= 1) {
f.timeOffset = f.times[0];
}
if (f.times.length >= 2) {
f.deltaTime = f.times[1] - f.times[0];
}
return Result.success(f);
}
export function parseLammpsTrajectory(data) {
return Task.create('Parse Lammp Trajectory', async (ctx) => {
return await parseInternal(data, ctx);
});
}