Ver Fonte

mol2 stub

JonStargaryen há 4 anos atrás
pai
commit
895e2ede2d

+ 23 - 0
src/mol-io/writer/mol2.ts

@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
+ */
+
+import { Mol2Encoder } from './mol2/encoder';
+import { Encoder } from './cif/encoder';
+
+export namespace Mol2Writer {
+    export interface EncoderParams {
+        encoderName?: string,
+        // whether to write ModelServer meta-information (query & params)
+        metaInformation?: boolean,
+        // whether to write hydrogen atoms
+        hydrogens?: boolean
+    }
+
+    export function createEncoder(params?: EncoderParams): Encoder {
+        const { encoderName = 'mol*', metaInformation = false, hydrogens = false } = params || {};
+        return new Mol2Encoder(encoderName, metaInformation, hydrogens);
+    }
+}

+ 244 - 0
src/mol-io/writer/mol2/encoder.ts

@@ -0,0 +1,244 @@
+/**
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
+ */
+
+import { StringBuilder, deepEqual } from '../../../mol-util';
+import Writer from '../writer';
+import { Encoder, Category, Field } from '../cif/encoder';
+import { getCategoryInstanceData } from '../cif/encoder/util';
+import { ComponentBond } from '../../../mol-model-formats/structure/property/bonds/comp';
+
+// specification: http://chemyang.ccnu.edu.cn/ccb/server/AIMMS/mol2.pdf
+export class Mol2Encoder implements Encoder<string> {
+    private builder: StringBuilder;
+    private meta: StringBuilder;
+    private encoded = false;
+    private error = false;
+    private componentData: ComponentBond;
+    readonly isBinary = false;
+    binaryEncodingProvider = void 0;
+
+    setComponentBondData(componentData: ComponentBond) {
+        this.componentData = componentData;
+    }
+
+    writeTo(stream: Writer) {
+        const chunks = StringBuilder.getChunks(this.builder);
+        for (let i = 0, _i = chunks.length; i < _i; i++) {
+            stream.writeString(chunks[i]);
+        }
+    }
+
+    getSize() {
+        return StringBuilder.getSize(this.builder);
+    }
+
+    getData() {
+        return StringBuilder.getString(this.builder);
+    }
+
+    startDataBlock() {
+
+    }
+
+    writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx) {
+        if (this.encoded) {
+            throw new Error('The writer contents have already been encoded, no more writing.');
+        }
+
+        if (this.metaInformation && (category.name === 'model_server_result' || category.name === 'model_server_params' || category.name === 'model_server_stats')) {
+            this.writeFullCategory(this.meta, category, context);
+            return;
+        }
+
+        // if error: force writing of meta information
+        if (category.name === 'model_server_error') {
+            this.writeFullCategory(this.meta, category, context);
+            this.error = true;
+            return;
+        }
+
+        // only care about atom_site category when writing SDF
+        if (category.name !== 'atom_site') {
+            return;
+        }
+
+        // use separate builder because we still need to write Counts and Bonds line
+        const ctab = StringBuilder.create();
+        const bonds = StringBuilder.create();
+        const charges = StringBuilder.create();
+
+        // write Atom block and gather data for Bonds and Charges
+        const { instance, source } = getCategoryInstanceData(category, context);
+        const sortedFields = this.getSortedFields(instance, ['Cartn_x', 'Cartn_y', 'Cartn_z', 'type_symbol']);
+        const label_comp_id = this.getField(instance, 'label_comp_id');
+        const label_atom_id = this.getField(instance, 'label_atom_id');
+
+        // all of this is used to ensure that only 1 residue is written
+        // TODO potentially we could use a dedicated query mode or use the 'correct' SDF definition and allow an arbitrary number of molecules in a file
+        const auxiliaryFields = this.getSortedFields(instance, ['label_seq_id', 'label_asym_id', 'pdbx_PDB_ins_code', 'pdbx_PDB_model_num']);
+
+        // write header
+        const name = label_comp_id.value(source[0].keys().move(), source[0].data, 0) as string;
+        StringBuilder.write(this.builder, `# Name: ${name}\n# Created by ${this.encoder}\n\n`);
+
+        const bondMap = this.componentData.entries.get(name)!;
+        let bondCount = 0;
+
+        // traverse once to determine all actually present atoms
+        const atoms = this.getAtoms(source, sortedFields, label_atom_id, auxiliaryFields, ctab);
+        for (let i1 = 0, il = atoms.length; i1 < il; i1++) {
+            bondMap.map.get(atoms[i1])!.forEach((v, k) => {
+                const i2 = atoms.indexOf(k);
+                const label2 = this.getLabel(k);
+                if (i1 < i2 && atoms.indexOf(k) > -1 && !this.skipHydrogen(label2)) {
+                    const { order } = v;
+                    StringBuilder.writeIntegerPadLeft(bonds, i1 + 1, 3);
+                    StringBuilder.writeIntegerPadLeft(bonds, i2 + 1, 3);
+                    StringBuilder.writeIntegerPadLeft(bonds, order, 3);
+                    StringBuilder.writeSafe(bonds, '  0  0  0  0\n');
+                    // TODO 2nd value: Single bonds: 0 = not stereo, 1 = Up, 4 = Either, 6 = Down, Double bonds: 0 = Use x-, y-, z-coords from atom block to determine cis or trans, 3 = Cis or trans (either) double bond
+                    bondCount++;
+                }
+            });
+        }
+
+        // write counts line
+        StringBuilder.writeIntegerPadLeft(this.builder, atoms.length, 3);
+        StringBuilder.writeIntegerPadLeft(this.builder, bondCount, 3);
+        StringBuilder.write(this.builder, '  0     0  0  0  0  0  0999 V2000\n');
+        // TODO 2nd value: chiral flag: 0=not chiral, 1=chiral
+
+        StringBuilder.writeSafe(this.builder, StringBuilder.getString(ctab));
+        StringBuilder.writeSafe(this.builder, StringBuilder.getString(bonds));
+        StringBuilder.writeSafe(this.builder, StringBuilder.getString(charges));
+        // TODO charges?
+
+        StringBuilder.writeSafe(this.builder, 'M  END\n');
+    }
+
+    private getAtoms(source: any, fields: Field<any, any>[], label_atom_id: Field<any, any>, auxiliaryFields: Field<any, any>[], ctab: StringBuilder): string[] {
+        const atoms = [];
+        let index = 0;
+        let id;
+
+        // is this loop needed?
+        l: for (let _c = 0; _c < source.length; _c++) {
+            const src = source[_c];
+            const data = src.data;
+
+            if (src.rowCount === 0) continue;
+
+            const it = src.keys();
+            while (it.hasNext)  {
+                const key = it.move();
+
+                // ensure only a single residue is written
+                // TODO fairly certain this can be done at query level
+                if (!id) {
+                    id = auxiliaryFields.map(af => af.value(key, data, 0));
+                } else {
+                    if (!deepEqual(id, auxiliaryFields.map(af => af.value(key, data, index)))) {
+                        break l;
+                    }
+                }
+
+                const lai = label_atom_id.value(key, data, index) as string;
+                const label = this.getLabel(lai);
+                if (this.skipHydrogen(label)) {
+                    index++;
+                    continue;
+                }
+                atoms.push(lai);
+
+                for (let _f = 0, _fl = fields.length; _f < _fl; _f++) {
+                    const f: Field<any, any> = fields[_f]!;
+                    const v = f.value(key, data, index);
+                    this.writeValue(ctab, v, f.type);
+                }
+
+                StringBuilder.writeSafe(ctab, '  0  0  0  0  0  0  0  0  0  0  0  0\n');
+                index++;
+            }
+        }
+
+        return atoms;
+    }
+
+    private skipHydrogen(label: string) {
+        if (this.hydrogens) {
+            return false;
+        }
+        return label.startsWith('H');
+    }
+
+    private getLabel(s: string) {
+        return s.replace(/[^A-Z]+/g, '');
+    }
+
+    private writeFullCategory<Ctx>(sb: StringBuilder, category: Category<Ctx>, context?: Ctx) {
+        const { instance, source } = getCategoryInstanceData(category, context);
+        const fields = instance.fields;
+        const src = source[0];
+        const data = src.data;
+
+        const it = src.keys();
+        const key = it.move();
+        for (let _f = 0; _f < fields.length; _f++) {
+            const f = fields[_f]!;
+
+            StringBuilder.writeSafe(sb, `> <${category.name}.${f.name}>\n`);
+            const val = f.value(key, data, 0);
+            StringBuilder.writeSafe(sb, val as string);
+            StringBuilder.writeSafe(sb, '\n\n');
+        }
+    }
+
+    private writeValue(sb: StringBuilder, val: string | number, t: Field.Type, floatPrecision: number = 4) {
+        if (t === Field.Type.Str) {
+            // type_symbol is the only string field - width 2, right-padded
+            StringBuilder.whitespace1(sb);
+            StringBuilder.writePadRight(sb, val as string, 2);
+        } else if (t === Field.Type.Int) {
+            StringBuilder.writeInteger(sb, val as number);
+        } else {
+            // coordinates have width 10 and are left-padded
+            StringBuilder.writePadLeft(sb, (val as number).toFixed(floatPrecision), 10);
+        }
+    }
+
+    private getSortedFields<Ctx>(instance: Category.Instance<Ctx>, names: string[]) {
+        return names.map(n => this.getField(instance, n));
+    }
+
+    private getField<Ctx>(instance: Category.Instance<Ctx>, name: string) {
+        return instance.fields.find(f => f.name === name)!;
+    }
+
+    encode() {
+        // write meta-information, do so after ctab
+        if (this.error || this.metaInformation) {
+            StringBuilder.writeSafe(this.builder, StringBuilder.getString(this.meta));
+        }
+
+        // terminate file
+        StringBuilder.writeSafe(this.builder, '$$$$\n');
+
+        this.encoded = true;
+    }
+
+    setFilter(filter?: Category.Filter) {}
+
+    setFormatter(formatter?: Category.Formatter) {}
+
+    isCategoryIncluded(name: string) {
+        return true;
+    }
+
+    constructor(readonly encoder: string, readonly metaInformation: boolean, readonly hydrogens: boolean) {
+        this.builder = StringBuilder.create();
+        this.meta = StringBuilder.create();
+    }
+}

+ 1 - 1
src/mol-io/writer/sdf.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
  */

+ 1 - 1
src/mol-io/writer/sdf/encoder.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
  */

+ 6 - 2
src/servers/model/server/query.ts

@@ -8,6 +8,7 @@ import * as path from 'path';
 import { Column } from '../../../mol-data/db';
 import { CifWriter } from '../../../mol-io/writer/cif';
 import { SdfWriter } from '../../../mol-io/writer/sdf';
+import { Mol2Writer } from '../../../mol-io/writer/mol2';
 import { Structure, StructureQuery, StructureSelection } from '../../../mol-model/structure';
 import { encode_mmCIF_categories } from '../../../mol-model/structure/export/mmcif';
 import { Progress } from '../../../mol-task';
@@ -25,6 +26,7 @@ import { Encoder } from '../../../mol-io/writer/cif/encoder';
 import { Encoding } from './api';
 import { ComponentBond } from '../../../mol-model-formats/structure/property/bonds/comp';
 import { SdfEncoder } from '../../../mol-io/writer/sdf/encoder';
+import { Mol2Encoder } from '../../../mol-io/writer/mol2/encoder';
 
 export interface Stats {
     structure: StructureWrapper,
@@ -65,7 +67,9 @@ function createEncoder(job: Job): Encoder {
             });
         case 'mol2':
             ensureCompatibleQueryType(job);
-            throw Error('impl me');
+            return Mol2Writer.createEncoder({
+                encoderName: `ModelServer ${Version}`
+            });
         default:
             return CifWriter.createEncoder({
                 binary: false,
@@ -207,7 +211,7 @@ async function resolveJobEntry(entry: JobEntry, structure: StructureWrapper, enc
         encoder.writeCategory(_model_server_result, entry);
         encoder.writeCategory(_model_server_params, entry);
 
-        if (encoder instanceof SdfEncoder) encoder.setComponentBondData(ComponentBond.Provider.get(structure.models[0])!);
+        if (encoder instanceof SdfEncoder || encoder instanceof Mol2Encoder) encoder.setComponentBondData(ComponentBond.Provider.get(structure.models[0])!);
         if (!entry.copyAllCategories && entry.queryDefinition.filter) encoder.setFilter(entry.queryDefinition.filter);
         if (result.length > 0) encode_mmCIF_categories(encoder, result, { copyAllCategories: entry.copyAllCategories });
         if (!entry.copyAllCategories && entry.queryDefinition.filter) encoder.setFilter();