diff --git a/src/mol-math/geometry/symmetry-operator.ts b/src/mol-math/geometry/symmetry-operator.ts index 6c233e11d5b49f3d544893a42d8fad372486a181..86884a935653dd7c49a68632d93e885a93cd3829 100644 --- a/src/mol-math/geometry/symmetry-operator.ts +++ b/src/mol-math/geometry/symmetry-operator.ts @@ -30,6 +30,17 @@ namespace SymmetryOperator { return { name, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl }; } + export function checkIfRotationAndTranslation(rot: Mat3, offset: Vec3) { + const matrix = Mat4.identity(); + for (let i = 0; i < 3; i++) { + for (let j = 0; j < 3; j++) { + Mat4.setValue(matrix, i, j, Mat3.getValue(rot, i, j)); + } + } + Mat4.setTranslation(matrix, offset); + return Mat4.isRotationAndTranslation(matrix, RotationEpsilon); + } + export function ofRotationAndOffset(name: string, rot: Mat3, offset: Vec3) { const t = Mat4.identity(); for (let i = 0; i < 3; i++) { diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model/structure/model/formats/mmcif.ts index cb9ddfa8c7ec9ab019517bc8883c6f8d542a985e..b03ed96635abfcc1376fe4f17cd37b4dc453471e 100644 --- a/src/mol-model/structure/model/formats/mmcif.ts +++ b/src/mol-model/structure/model/formats/mmcif.ts @@ -66,7 +66,8 @@ function getNcsOperators(format: mmCIF_Format) { for (let i = 0; i < struct_ncs_oper._rowCount; i++) { const m = Tensor.toMat3(matrixSpace, matrix.value(i)); const v = Tensor.toVec3(vectorSpace, vector.value(i)); - opers[i] = SymmetryOperator.ofRotationAndOffset(`ncs_${id.value(i)}`, m, v); + if (!SymmetryOperator.checkIfRotationAndTranslation(m, v)) continue; + opers[opers.length] = SymmetryOperator.ofRotationAndOffset(`ncs_${id.value(i)}`, m, v); } return opers; } diff --git a/src/servers/model/preprocess.ts b/src/servers/model/preprocess.ts index c61288185032aebc8351e243f5c69f922d459590..b88e33348a3f19b63a57c948fa9ded34ff03f32e 100644 --- a/src/servers/model/preprocess.ts +++ b/src/servers/model/preprocess.ts @@ -4,28 +4,14 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import * as argparse from 'argparse' -import { preprocessFile } from './preprocess/preprocess'; +import * as cluster from 'cluster' +import { runChild } from './preprocess/parallel'; -const cmdParser = new argparse.ArgumentParser({ - addHelp: true, - description: 'Preprocess CIF files to include custom properties and convert them to BinaryCIF format.' -}); -cmdParser.addArgument(['--input', '-i'], { help: 'Input filename', required: true }); -cmdParser.addArgument(['--outCIF', '-oc'], { help: 'Output CIF filename', required: false }); -cmdParser.addArgument(['--outBCIF', '-ob'], { help: 'Output BinaryCIF filename', required: false }); - -// TODO: "bulk" mode - -interface CmdArgs { - input: string, - outCIF?: string, - outBCIF?: string +if (cluster.isMaster) { + require('./preprocess/master'); +} else { + runChild(); } -const cmdArgs = cmdParser.parseArgs() as CmdArgs; - -if (cmdArgs.input) preprocessFile(cmdArgs.input, cmdArgs.outCIF, cmdArgs.outBCIF); - // example: // node build\node_modules\servers\model\preprocess -i e:\test\Quick\1cbs_updated.cif -oc e:\test\mol-star\model\1cbs.cif -ob e:\test\mol-star\model\1cbs.bcif \ No newline at end of file diff --git a/src/servers/model/preprocess/converter.ts b/src/servers/model/preprocess/converter.ts index bad7f7d865f61d04ff2069c0525de30f79716a32..66d77fcb46558b4225929d1f31babb90af1f1093 100644 --- a/src/servers/model/preprocess/converter.ts +++ b/src/servers/model/preprocess/converter.ts @@ -7,7 +7,7 @@ import { CifCategory, CifField, CifFrame, getCifFieldType } from 'mol-io/reader/cif'; import { CifWriter } from 'mol-io/writer/cif'; import { Task } from 'mol-task'; -import { showProgress } from './util'; +// import { showProgress } from './util'; function getCategoryInstanceProvider(cat: CifCategory, fields: CifWriter.Field[]): CifWriter.Category { return { @@ -47,5 +47,5 @@ export function classifyCif(frame: CifFrame) { ret.push(getCategoryInstanceProvider(cat, fields)); } return ret; - }).run(showProgress, 250); + }).run(); } \ No newline at end of file diff --git a/src/servers/model/preprocess/master.ts b/src/servers/model/preprocess/master.ts new file mode 100644 index 0000000000000000000000000000000000000000..b058ad56f7a9b60a0530797ac281776fb369f4db --- /dev/null +++ b/src/servers/model/preprocess/master.ts @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import * as fs from 'fs' +import * as path from 'path' +import * as argparse from 'argparse' +import { preprocessFile } from './preprocess'; +import { ParallelPreprocessConfig, runMaster } from './parallel'; + +const cmdParser = new argparse.ArgumentParser({ + addHelp: true, + description: 'Preprocess CIF files to include custom properties and convert them to BinaryCIF format.' +}); +cmdParser.addArgument(['--input', '-i'], { help: 'Input filename', required: false }); +cmdParser.addArgument(['--outCIF', '-oc'], { help: 'Output CIF filename', required: false }); +cmdParser.addArgument(['--outBCIF', '-ob'], { help: 'Output BinaryCIF filename', required: false }); +cmdParser.addArgument(['--bulk', '-b'], { help: 'Bulk JSON ({ numProcesses?: number, entries: { source: string, cif?: string, bcif?: string }[] })', required: false }); +cmdParser.addArgument(['--folderIn', '-f'], { help: 'Convert folder', required: false }); +cmdParser.addArgument(['--folderOutCIF', '-foc'], { help: 'Convert folder text output', required: false }); +cmdParser.addArgument(['--folderOutBCIF', '-fob'], { help: 'Convert folder binary output', required: false }); +cmdParser.addArgument(['--folderNumProcesses', '-fp'], { help: 'Convert folder num processes', required: false }); + +interface CmdArgs { + bulk?: string, + input?: string, + outCIF?: string, + outBCIF?: string, + folderIn?: string, + folderOutCIF?: string, + folderOutBCIF?: string, + folderNumProcesses?: string +} + +const cmdArgs = cmdParser.parseArgs() as CmdArgs; + +if (cmdArgs.input) preprocessFile(cmdArgs.input, cmdArgs.outCIF, cmdArgs.outBCIF); +else if (cmdArgs.bulk) runBulk(cmdArgs.bulk); +else if (cmdArgs.folderIn) runFolder(cmdArgs); + +function runBulk(input: string) { + const config = JSON.parse(fs.readFileSync(input, 'utf8')) as ParallelPreprocessConfig; + runMaster(config); +} + +function runFolder(args: CmdArgs) { + const files = fs.readdirSync(args.folderIn!); + const config: ParallelPreprocessConfig = { numProcesses: +args.folderNumProcesses! || 1, entries: [] }; + const cifTest = /\.cif$/; + for (const f of files) { + if (!cifTest.test(f)) continue; + + config.entries.push({ + source: path.join(args.folderIn!, f), + cif: cmdArgs.folderOutCIF ? path.join(args.folderOutCIF!, f) : void 0, + bcif: cmdArgs.folderOutBCIF ? path.join(args.folderOutBCIF!, path.parse(f).name + '.bcif') : void 0, + }); + } + runMaster(config); +} + +// example: +// node build\node_modules\servers\model\preprocess -i e:\test\Quick\1cbs_updated.cif -oc e:\test\mol-star\model\1cbs.cif -ob e:\test\mol-star\model\1cbs.bcif \ No newline at end of file diff --git a/src/servers/model/preprocess/parallel.ts b/src/servers/model/preprocess/parallel.ts new file mode 100644 index 0000000000000000000000000000000000000000..83ece3ac4cb6f23b75bae64165a776239ffb04d1 --- /dev/null +++ b/src/servers/model/preprocess/parallel.ts @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import * as path from 'path' +import * as cluster from 'cluster' +import { now } from 'mol-task'; +import { PerformanceMonitor } from 'mol-util/performance-monitor'; +import { preprocessFile } from './preprocess'; + +export interface PreprocessEntry { + source: string, + cif?: string, + bcif?: string +} + +export interface ParallelPreprocessConfig { + numProcesses?: number, + entries: PreprocessEntry[] +} + +export function runMaster(config: ParallelPreprocessConfig) { + const parts = partitionArray(config.entries, config.numProcesses || 1); + // const numForks = Math.min(parts.length, config.numProcesses); + + const started = now(); + let progress = 0; + const onMessage = (msg: any) => { + if (msg.type === 'tick') { + progress++; + const elapsed = now() - started; + console.log(`[${progress}/${config.entries.length}] in ${PerformanceMonitor.format(elapsed)} (avg ${PerformanceMonitor.format(elapsed / progress)}).`); + } else if (msg.type === 'error') { + console.error(`${msg.id}: ${msg.error}`) + } + } + + for (const _ of parts) { + const worker = cluster.fork(); + worker.on('message', onMessage); + } + + let i = 0; + for (const id in cluster.workers) { + cluster.workers[id]!.send(parts[i++]); + } +} + +export function runChild() { + process.on('message', async (entries: PreprocessEntry[]) => { + for (const entry of entries) { + try { + await preprocessFile(entry.source, entry.cif, entry.bcif); + } catch (e) { + process.send!({ type: 'error', id: path.parse(entry.source).name, error: '' + e }); + } + process.send!({ type: 'tick' }); + } + process.exit(); + }); +} + +function partitionArray<T>(xs: T[], count: number): T[][] { + const ret: T[][] = []; + const s = Math.ceil(xs.length / count); + for (let i = 0; i < xs.length; i += s) { + const bucket: T[] = []; + for (let j = i, _j = Math.min(xs.length, i + s); j < _j; j++) { + bucket.push(xs[j]); + } + ret.push(bucket); + } + return ret; +} + diff --git a/src/servers/model/preprocess/preprocess.ts b/src/servers/model/preprocess/preprocess.ts index 394fe205ac81e4a8e2bbb908531dcf1e08c58a43..bc6b5abca1b3d748e6d2b06d0dcd0691d61ab225 100644 --- a/src/servers/model/preprocess/preprocess.ts +++ b/src/servers/model/preprocess/preprocess.ts @@ -6,58 +6,62 @@ import { readStructure } from '../server/structure-wrapper'; import { classifyCif } from './converter'; -import { ConsoleLogger } from 'mol-util/console-logger'; +// import { ConsoleLogger } from 'mol-util/console-logger'; import { Structure } from 'mol-model/structure'; import { CifWriter } from 'mol-io/writer/cif'; import Writer from 'mol-io/writer/writer'; import { wrapFileToWriter } from '../server/api-local'; -import { Task } from 'mol-task'; -import { showProgress, clearLine } from './util'; +import { Task/*, now*/ } from 'mol-task'; +import { /*showProgress, clearLine */ } from './util'; import { encode_mmCIF_categories, CifExportContext } from 'mol-model/structure/export/mmcif'; -// TODO: error handling, bulk mode +// TODO: error handling +// let linearId = 0; export async function preprocessFile(filename: string, outputCif?: string, outputBcif?: string) { - ConsoleLogger.log('ModelServer', `Reading ${filename}...`); + // linearId++; + + //const started = now(); + //ConsoleLogger.log(`${linearId}`, `Reading '${filename}'...`); const input = await readStructure('entry', '_local_', filename); - ConsoleLogger.log('ModelServer', `Classifying CIF categories...`); + //ConsoleLogger.log(`${linearId}`, `Classifying CIF categories...`); const categories = await classifyCif(input.cifFrame); - clearLine(); + //clearLine(); const exportCtx = CifExportContext.create(input.structure, input.structure.models[0]); if (outputCif) { - ConsoleLogger.log('ModelServer', `Encoding CIF...`); + //ConsoleLogger.log(`${linearId}`, `Encoding CIF...`); const writer = wrapFileToWriter(outputCif); const encoder = CifWriter.createEncoder({ binary: false }); await encode(input.structure, input.cifFrame.header, categories, encoder, exportCtx, writer); - clearLine(); + // clearLine(); writer.end(); } if (outputBcif) { - ConsoleLogger.log('ModelServer', `Encoding BinaryCIF...`); + // ConsoleLogger.log(`${linearId}`, `Encoding BinaryCIF...`); const writer = wrapFileToWriter(outputBcif); const encoder = CifWriter.createEncoder({ binary: true, binaryAutoClassifyEncoding: true }); await encode(input.structure, input.cifFrame.header, categories, encoder, exportCtx, writer); - clearLine(); + //clearLine(); writer.end(); } - ConsoleLogger.log('ModelServer', `Done.`); + // ConsoleLogger.log(`${linearId}`, `Finished '${filename}' in ${Math.round(now() - started)}ms`); } function encode(structure: Structure, header: string, categories: CifWriter.Category[], encoder: CifWriter.Encoder, exportCtx: CifExportContext, writer: Writer) { return Task.create('Encode', async ctx => { const skipCategoryNames = new Set<string>(categories.map(c => c.name)); encoder.startDataBlock(header); - let current = 0; + // let current = 0; for (const cat of categories){ encoder.writeCategory(cat); - current++; - if (ctx.shouldUpdate) await ctx.update({ message: 'Encoding...', current, max: categories.length }); + // current++; + // if (ctx.shouldUpdate) await ctx.update({ message: 'Encoding...', current, max: categories.length }); } encode_mmCIF_categories(encoder, structure, { skipCategoryNames, exportCtx }); encoder.encode(); encoder.writeTo(writer); - }).run(showProgress, 250); + }).run(); } \ No newline at end of file diff --git a/src/servers/model/properties.ts b/src/servers/model/properties.ts index fddf09e61fceea5f1f97ee5bc320eebac31ff351..5f292223862c4d3266daaf8e53333e8911241811 100644 --- a/src/servers/model/properties.ts +++ b/src/servers/model/properties.ts @@ -6,14 +6,14 @@ */ import { Model } from 'mol-model/structure'; -import { PDBe_structureQualityReport } from './properties/pdbe'; -import { RCSB_assemblySymmetry } from './properties/rcsb'; +//import { PDBe_structureQualityReport } from './properties/pdbe'; +//import { RCSB_assemblySymmetry } from './properties/rcsb'; export function attachModelProperties(model: Model): Promise<any>[] { // return a list of promises that start attaching the props in parallel // (if there are downloads etc.) return [ - PDBe_structureQualityReport(model), - RCSB_assemblySymmetry(model) + //PDBe_structureQualityReport(model), + //RCSB_assemblySymmetry(model) ]; } \ No newline at end of file