Browse Source

refactoring

JonStargaryen 4 years ago
parent
commit
bc6d2112e2

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

@@ -1,244 +0,0 @@
-/**
- * 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();
-    }
-}

+ 21 - 0
src/servers/model/ligand-writer/mol.ts

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

+ 24 - 21
src/mol-io/writer/sdf/encoder.ts → src/servers/model/ligand-writer/mol/encoder.ts

@@ -4,14 +4,16 @@
  * @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';
+import { StringBuilder, deepEqual } from '../../../../mol-util';
+import Writer from '../../../../mol-io/writer/writer';
+import { Encoder, Category, Field } from '../../../../mol-io/writer/cif/encoder';
+import { getCategoryInstanceData } from '../../../../mol-io/writer/cif/encoder/util';
+import { ComponentBond } from '../../../../mol-model-formats/structure/property/bonds/comp';
 
 // specification: http://c4.cabrillo.edu/404/ctfile.pdf
-export class SdfEncoder implements Encoder<string> {
+// SDF wraps MOL and allows for multiple molecules per file as well as additional properties
+// TODO add support for stereo/chiral flags, add charges
+export class MolEncoder implements Encoder<string> {
     private builder: StringBuilder;
     private meta: StringBuilder;
     private encoded = false;
@@ -68,7 +70,6 @@ export class SdfEncoder implements Encoder<string> {
         // 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);
@@ -82,7 +83,8 @@ export class SdfEncoder implements Encoder<string> {
 
         // write header
         const name = label_comp_id.value(source[0].keys().move(), source[0].data, 0) as string;
-        StringBuilder.write(this.builder, `${name}\n  Created by ${this.encoder}\n\n`);
+        // 3rd lines must be present and can contain comments
+        StringBuilder.write(this.builder, `${name}\n  ${this.encoder}\n\n`);
 
         const bondMap = this.componentData.entries.get(name)!;
         let bondCount = 0;
@@ -99,8 +101,6 @@ export class SdfEncoder implements Encoder<string> {
                     StringBuilder.writeIntegerPadLeft(bonds, i2 + 1, 3);
                     StringBuilder.writeIntegerPadLeft(bonds, order, 3);
                     StringBuilder.writeSafe(bonds, '  0  0  0  0\n');
-                    // TODO 3rd value: charge - 0 = uncharged or value other than these, 1 = +3, 2 = +2, 3 = +1, 4 = doublet radical, 5 = -1, 6 = -2, 7 = -3
-                    // TODO optional 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++;
                 }
             });
@@ -110,12 +110,9 @@ export class SdfEncoder implements Encoder<string> {
         StringBuilder.writeIntegerPadLeft(this.builder, atoms.length, 3);
         StringBuilder.writeIntegerPadLeft(this.builder, bondCount, 3);
         StringBuilder.write(this.builder, '  0  0  0  0  0  0  0  0  0\n');
-        // TODO optional 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 optional charges
 
         StringBuilder.writeSafe(this.builder, 'M  END\n');
     }
@@ -123,9 +120,9 @@ export class SdfEncoder implements Encoder<string> {
     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;
+        let id: (string | number)[];
 
-        // is this loop needed?
+        // is outer loop even needed?
         l: for (let _c = 0; _c < source.length; _c++) {
             const src = source[_c];
             const data = src.data;
@@ -224,21 +221,27 @@ export class SdfEncoder implements Encoder<string> {
             StringBuilder.writeSafe(this.builder, StringBuilder.getString(this.meta));
         }
 
-        // terminate file
-        StringBuilder.writeSafe(this.builder, '$$$$\n');
+        // terminate file (needed for SDF only)
+        if (!!this.terminator) {
+            StringBuilder.writeSafe(this.builder, `${this.terminator}\n`);
+        }
 
         this.encoded = true;
     }
 
-    setFilter(filter?: Category.Filter) {}
+    setFilter() {}
 
-    setFormatter(formatter?: Category.Formatter) {}
+    setFormatter() {}
 
-    isCategoryIncluded(name: string) {
+    isCategoryIncluded() {
         return true;
     }
 
-    constructor(readonly encoder: string, readonly metaInformation: boolean, readonly hydrogens: boolean) {
+    constructor(readonly encoder: string, readonly metaInformation: boolean, readonly hydrogens: boolean, readonly terminator: string) {
+        if (metaInformation && !!terminator) {
+            throw new Error('meta-information cannot be written for MOL files');
+        }
+        
         this.builder = StringBuilder.create();
         this.meta = StringBuilder.create();
     }

+ 1 - 1
src/mol-io/writer/mol2.ts → src/servers/model/ligand-writer/mol2.ts

@@ -5,7 +5,7 @@
  */
 
 import { Mol2Encoder } from './mol2/encoder';
-import { Encoder } from './cif/encoder';
+import { Encoder } from '../../../mol-io/writer/cif/encoder';
 
 export namespace Mol2Writer {
     export interface EncoderParams {

+ 11 - 0
src/servers/model/ligand-writer/mol2/encoder.ts

@@ -0,0 +1,11 @@
+/**
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
+ */
+
+import { Encoder } from "../../../../mol-io/writer/cif/encoder";
+
+// specification: http://chemyang.ccnu.edu.cn/ccb/server/AIMMS/mol2.pdf
+export class Mol2Encoder implements Encoder<string> {
+}

+ 3 - 3
src/mol-io/writer/sdf.ts → src/servers/model/ligand-writer/sdf.ts

@@ -4,8 +4,8 @@
  * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
  */
 
-import { SdfEncoder } from './sdf/encoder';
-import { Encoder } from './cif/encoder';
+import { MolEncoder } from './mol/encoder';
+import { Encoder } from '../../../mol-io/writer/cif/encoder';
 
 export namespace SdfWriter {
     export interface EncoderParams {
@@ -18,6 +18,6 @@ export namespace SdfWriter {
 
     export function createEncoder(params?: EncoderParams): Encoder {
         const { encoderName = 'mol*', metaInformation = false, hydrogens = false } = params || {};
-        return new SdfEncoder(encoderName, metaInformation, hydrogens);
+        return new MolEncoder(encoderName, metaInformation, hydrogens, '$$$$');
     }
 }

+ 4 - 2
src/servers/model/server/api.ts

@@ -44,12 +44,12 @@ export interface QueryDefinition<Params = any> {
 
 export const CommonQueryParamsInfo: QueryParamInfo[] = [
     { name: 'model_nums', type: QueryParamType.String, description: `A comma-separated list of model ids (i.e. 1,2). If set, only include atoms with the corresponding '_atom_site.pdbx_PDB_model_num' field.` },
-    { name: 'encoding', type: QueryParamType.String, defaultValue: 'cif', description: `Determines the output encoding (text based 'CIF' or binary 'BCIF'). Ligands can also be exported as 'SDF' or 'MOL2'.`, supportedValues: ['cif', 'bcif', 'mol2', 'sdf'] },
+    { name: 'encoding', type: QueryParamType.String, defaultValue: 'cif', description: `Determines the output encoding (text based 'CIF' or binary 'BCIF'). Ligands can also be exported as 'SDF' or 'MOL2'.`, supportedValues: ['cif', 'bcif', 'sdf', 'mol', 'mol2'] },
     { name: 'copy_all_categories', type: QueryParamType.Boolean, defaultValue: false, description: 'If true, copy all categories from the input file.' },
     { name: 'data_source', type: QueryParamType.String, defaultValue: '', description: 'Allows to control how the provided data source ID maps to input file (as specified by the server instance config).' }
 ];
 
-export type Encoding = 'cif' | 'bcif' | 'mol2' | 'sdf';
+export type Encoding = 'cif' | 'bcif' | 'sdf' | 'mol' | 'mol2';
 export interface CommonQueryParamsInfo {
     model_nums?: number[],
     encoding?: Encoding,
@@ -284,6 +284,8 @@ function mapEncoding(value: string) {
     switch (value) {
         case 'bcif':
             return 'bcif';
+        case 'mol':
+            return 'mol';
         case 'mol2':
             return 'mol2';
         case 'sdf':

+ 14 - 5
src/servers/model/server/query.ts

@@ -52,28 +52,37 @@ export async function resolveJob(job: Job) {
     }
 }
 
+const SharedParams = {
+    encoderName: `ModelServer ${Version}`
+}
+
 function createEncoder(job: Job): Encoder {
     switch (job.responseFormat.encoding) {
         case 'bcif':
             return CifWriter.createEncoder({
+                ...SharedParams,
                 binary: true,
-                encoderName: `ModelServer ${Version}`,
                 binaryAutoClassifyEncoding: true
             });
         case 'sdf':
             ensureCompatibleQueryType(job);
             return SdfWriter.createEncoder({
-                encoderName: `ModelServer ${Version}`
+                ...SharedParams
             });
+        case 'mol':
+            ensureCompatibleQueryType(job);
+            return Mol2Writer.createEncoder({
+                ...SharedParams
+            })
         case 'mol2':
             ensureCompatibleQueryType(job);
             return Mol2Writer.createEncoder({
-                encoderName: `ModelServer ${Version}`
+                ...SharedParams
             });
         default:
             return CifWriter.createEncoder({
+                ...SharedParams,
                 binary: false,
-                encoderName: `ModelServer ${Version}`,
                 binaryAutoClassifyEncoding: true
             });
     }
@@ -82,7 +91,7 @@ function createEncoder(job: Job): Encoder {
 function ensureCompatibleQueryType(job: Job) {
     job.entries.forEach(e => {
         if (e.queryDefinition.niceName !== 'Ligand') {
-            throw Error("sdf and mol2 encoding are only available for queries of type 'Ligand'");
+            throw Error("sdf, mol and mol2 encoding are only available for queries of type 'Ligand'");
         }
     });
 }